Refactor wsssht.c: Split large monolithic file into modular components

- Created libwsssht/ directory with modular components:
  * utils.h/c: Utility functions (print_usage, parse_connection_string, parse_args)
  * modes.h/c: Mode-specific functions (bridge, script, daemon modes)
  * threads.h/c: Thread-related functions and structures
  * wsssht.h: Main header with includes and declarations

- Reduced wsssht.c from 2769 lines to 674 lines (main function only)
- Updated Makefile and configure.sh to handle new modular structure
- Maintained exact same functionality and command-line interface
- Improved code maintainability and organization

All functionality preserved, build successful, and wsssht binary works correctly.
parent 8b1388b8
...@@ -56,7 +56,7 @@ CFLAGS = -Wall -Wextra -O2 -D_GNU_SOURCE $(shell pkg-config --cflags openssl) ...@@ -56,7 +56,7 @@ CFLAGS = -Wall -Wextra -O2 -D_GNU_SOURCE $(shell pkg-config --cflags openssl)
LDFLAGS = $(shell pkg-config --libs openssl) LDFLAGS = $(shell pkg-config --libs openssl)
# Source files # Source files
LIB_SRCS = wssshlib.c websocket.c wssh_ssl.c tunnel.c LIB_SRCS = wssshlib.c websocket.c wssh_ssl.c tunnel.c libwsssht/utils.c libwsssht/modes.c libwsssht/threads.c
LIB_OBJS = $(LIB_SRCS:.c=.o) LIB_OBJS = $(LIB_SRCS:.c=.o)
SRCS = wssshc.c wsssh.c wsscp.c wsssht.c SRCS = wssshc.c wsssh.c wsscp.c wsssht.c
OBJS = $(SRCS:.c=.o) OBJS = $(SRCS:.c=.o)
...@@ -69,13 +69,13 @@ MANPAGES = man/wssshc.1 man/wsssh.1 man/wsscp.1 man/wsssht.1 ...@@ -69,13 +69,13 @@ MANPAGES = man/wssshc.1 man/wsssh.1 man/wsscp.1 man/wsssht.1
all: $(TARGETS) all: $(TARGETS)
# Individual targets # Individual targets
wssshc: wssshc.o $(LIB_OBJS) wssshc: wssshc.o wssshlib.o websocket.o wssh_ssl.o tunnel.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsssh: wsssh.o $(LIB_OBJS) wsssh: wsssh.o wssshlib.o websocket.o wssh_ssl.o tunnel.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsscp: wsscp.o $(LIB_OBJS) wsscp: wsscp.o wssshlib.o websocket.o wssh_ssl.o tunnel.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsssht: wsssht.o $(LIB_OBJS) wsssht: wsssht.o $(LIB_OBJS)
...@@ -87,7 +87,7 @@ wsssht: wsssht.o $(LIB_OBJS) ...@@ -87,7 +87,7 @@ wsssht: wsssht.o $(LIB_OBJS)
# Clean # Clean
clean: clean:
rm -f $(OBJS) $(LIB_OBJS) $(TARGETS) rm -f $(OBJS) $(LIB_OBJS) $(TARGETS) libwsssht/*.o
# Install (optional) # Install (optional)
install: all install: all
......
/*
* WebSocket SSH Tunnel (wsssht) - Mode Functions Implementation
* Mode-specific functions for wsssht
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <getopt.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/select.h>
#include <time.h>
#include <errno.h>
#include "../wssshlib.h"
#include "../websocket.h"
#include "../wssh_ssl.h"
#include "../tunnel.h"
#include "modes.h"
int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) {
// Bridge mode: Pure transport layer - no tunnel setup, just WebSocket transport
if (config->debug) {
printf("[DEBUG] Starting bridge mode (pure transport) with client_id=%s, host=%s, port=%d\n",
client_id, wssshd_host, wssshd_port);
fflush(stdout);
}
// Send initial status
printf("{\"type\":\"bridge_started\",\"client_id\":\"%s\",\"host\":\"%s\",\"port\":%d,\"timestamp\":%ld}\n",
client_id, wssshd_host, wssshd_port, time(NULL));
fflush(stdout);
// Establish WebSocket connection (no tunnel setup)
SSL_CTX *ws_ctx = NULL;
int ws_sock = setup_websocket_connection(wssshd_host, wssshd_port, client_id, config->debug, &ws_ctx);
if (ws_sock < 0) {
printf("{\"type\":\"error\",\"message\":\"Failed to establish WebSocket connection\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
if (ws_ctx) SSL_CTX_free(ws_ctx);
return 1;
}
// Get the SSL connection for sending messages
SSL *ws_ssl = active_tunnel ? active_tunnel->ssl : NULL;
if (!ws_ssl) {
printf("{\"type\":\"error\",\"message\":\"Failed to get SSL connection\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
close(ws_sock);
if (ws_ctx) SSL_CTX_free(ws_ctx);
return 1;
}
// Send connection established message
printf("{\"type\":\"websocket_connected\",\"socket\":%d,\"timestamp\":%ld}\n", ws_sock, time(NULL));
fflush(stdout);
// Main bridge loop - pure transport layer
char buffer[BUFFER_SIZE];
fd_set readfds;
struct timeval tv;
// Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0;
printf("{\"type\":\"bridge_ready\",\"message\":\"Pure transport layer active\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
while (1) {
// Check for stdin input (messages to send to server)
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
FD_SET(ws_sock, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 50000; // 50ms timeout
int max_fd = (ws_sock > STDIN_FILENO) ? ws_sock : STDIN_FILENO;
int retval = select(max_fd + 1, &readfds, NULL, NULL, &tv);
if (retval > 0) {
// Handle stdin input (messages to send to server)
if (FD_ISSET(STDIN_FILENO, &readfds)) {
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
// EOF or error on stdin
printf("{\"type\":\"stdin_closed\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
// Remove trailing newline
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
len--;
}
if (len > 0) {
// Determine channel based on message content
const char *channel = "control";
if (strstr(buffer, "\"type\":\"tunnel_data\"") || strstr(buffer, "tunnel_data")) {
channel = "data";
}
// Send message to appropriate WebSocket channel using SSL
if (send_websocket_frame(ws_ssl, buffer)) {
printf("{\"type\":\"message_sent\",\"channel\":\"%s\",\"message\":\"%s\",\"timestamp\":%ld}\n",
channel, buffer, time(NULL));
fflush(stdout);
} else {
printf("{\"type\":\"send_error\",\"channel\":\"%s\",\"timestamp\":%ld}\n", channel, time(NULL));
fflush(stdout);
}
}
}
// Handle WebSocket data (messages from server)
if (FD_ISSET(ws_sock, &readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Use SSL to read WebSocket data
int bytes_read = SSL_read(ws_ssl, frame_buffer + frame_buffer_used,
sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read > 0) {
frame_buffer_used += bytes_read;
if (config->debug) {
printf("[DEBUG - Bridge] Accumulated %d bytes from WebSocket\n", frame_buffer_used);
fflush(stdout);
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer for processing
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
// Determine channel and forward message
const char *channel = "control";
if (strstr(buffer, "\"type\":\"tunnel_data\"") || strstr(buffer, "tunnel_data")) {
channel = "data";
}
printf("{\"type\":\"message_received\",\"channel\":\"%s\",\"message\":\"%s\",\"timestamp\":%ld}\n",
channel, buffer, time(NULL));
fflush(stdout);
}
} else if (frame_type == 0x88) { // Close frame
printf("{\"type\":\"websocket_close\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
} else if (frame_type == 0x89) { // Ping frame
printf("{\"type\":\"ping_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
// Send pong
if (!send_pong_frame(ws_ssl, payload, payload_len)) {
printf("{\"type\":\"pong_send_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
} else if (frame_type == 0x8A) { // Pong frame
printf("{\"type\":\"pong_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
}
} else if (bytes_read == 0) {
// Connection closed
printf("{\"type\":\"websocket_connection_closed\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
} else {
// Error
printf("{\"type\":\"websocket_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
}
}
}
// Small delay to prevent busy looping
usleep(25000); // 25ms
}
// Cleanup
close(ws_sock);
// Clean up SSL resources for bridge mode
if (active_tunnel && active_tunnel->ssl) {
SSL_free(active_tunnel->ssl);
active_tunnel->ssl = NULL;
}
if (ws_ctx) {
SSL_CTX_free(ws_ctx);
ws_ctx = NULL;
}
printf("{\"type\":\"bridge_ended\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
return 0;
}
int run_script_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) {
// Script mode: JSON protocol for scripting
if (config->debug) {
printf("[DEBUG] Starting script mode with client_id=%s, host=%s, port=%d\n",
client_id, wssshd_host, wssshd_port);
fflush(stdout);
}
// Send initial status for scripting
printf("{\"type\":\"script_started\",\"client_id\":\"%s\",\"host\":\"%s\",\"port\":%d,\"tunnel_host\":\"%s\",\"tunnel_port\":\"%s\",\"service\":\"%s\",\"timestamp\":%ld}\n",
client_id, wssshd_host, wssshd_port,
config->tunnel_host ? config->tunnel_host : "127.0.0.1",
config->local_port ? config->local_port : "auto",
config->service ? config->service : "ssh",
time(NULL));
fflush(stdout);
// Establish tunnel
int listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, config->local_port ? atoi(config->local_port) : find_available_port(),
config->debug, 0, config->tunnel_host);
if (listen_sock < 0) {
printf("{\"type\":\"script_error\",\"message\":\"Failed to establish tunnel\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
return 1;
}
// Send tunnel established message
printf("{\"type\":\"tunnel_ready\",\"listen_sock\":%d,\"timestamp\":%ld}\n", listen_sock, time(NULL));
fflush(stdout);
// Wait for 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) {
printf("{\"type\":\"script_error\",\"message\":\"Failed to accept connection\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
close(listen_sock);
return 1;
}
close(listen_sock);
// Send connection accepted message
printf("{\"type\":\"connection_established\",\"socket\":%d,\"timestamp\":%ld}\n", accepted_sock, time(NULL));
fflush(stdout);
// Send tunnel status information
printf("{\"type\":\"tunnel_status\",\"status\":\"active\",\"local_socket\":%d,\"request_id\":\"%s\",\"timestamp\":%ld}\n",
accepted_sock, active_tunnel->request_id, time(NULL));
fflush(stdout);
// Send transport information if available
if (active_tunnel) {
printf("{\"type\":\"transport_info\",\"data_channel\":\"%s\",\"control_channel\":\"%s\",\"timestamp\":%ld}\n",
config->tunnel ? config->tunnel : "any",
config->tunnel_control ? config->tunnel_control : "any",
time(NULL));
fflush(stdout);
}
// Set up tunnel for accepted connection
pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = accepted_sock;
// Send any buffered data info
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
printf("{\"type\":\"buffer_available\",\"bytes\":%zu,\"timestamp\":%ld}\n", active_tunnel->incoming_buffer->used, time(NULL));
fflush(stdout);
}
pthread_mutex_unlock(&tunnel_mutex);
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (thread_args) {
thread_args->ssl = active_tunnel->ssl;
thread_args->tunnel = active_tunnel;
thread_args->debug = config->debug;
pthread_t thread;
pthread_create(&thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(thread);
}
// Script mode main loop - handle WebSocket messages and script commands
printf("{\"type\":\"script_ready\",\"message\":\"Script mode active\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
// Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0;
// Buffer for processing messages
char buffer[BUFFER_SIZE];
int last_status_time = time(NULL);
while (1) {
// Check tunnel status
pthread_mutex_lock(&tunnel_mutex);
int tunnel_active = active_tunnel && active_tunnel->active;
int tunnel_broken = active_tunnel && active_tunnel->broken;
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
int ssl_fd = current_ssl ? SSL_get_fd(current_ssl) : -1;
pthread_mutex_unlock(&tunnel_mutex);
if (!tunnel_active) {
if (tunnel_broken) {
printf("{\"type\":\"tunnel_broken\",\"timestamp\":%ld}\n", time(NULL));
} else {
printf("{\"type\":\"tunnel_closed\",\"timestamp\":%ld}\n", time(NULL));
}
fflush(stdout);
break;
}
// Send periodic status updates every 30 seconds
int current_time = time(NULL);
if (current_time - last_status_time >= 30) {
printf("{\"type\":\"status\",\"message\":\"Tunnel active\",\"uptime\":%d,\"timestamp\":%d}\n",
current_time - last_status_time, (int)current_time);
fflush(stdout);
last_status_time = current_time;
}
// Check for stdin input and WebSocket data
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
if (ssl_fd >= 0) {
FD_SET(ssl_fd, &readfds);
}
tv.tv_sec = 0;
tv.tv_usec = 100000; // 100ms timeout
int max_fd = (ssl_fd > STDIN_FILENO) ? ssl_fd : STDIN_FILENO;
int retval = select(max_fd + 1, &readfds, NULL, NULL, &tv);
if (retval > 0) {
// Handle stdin input (script commands)
if (FD_ISSET(STDIN_FILENO, &readfds)) {
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
// EOF or error on stdin
printf("{\"type\":\"script_input_closed\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
// Remove trailing newline
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
}
// Process script command
printf("{\"type\":\"script_command\",\"command\":\"%s\",\"timestamp\":%ld}\n", buffer, time(NULL));
fflush(stdout);
// Handle basic script commands
if (strcmp(buffer, "status") == 0) {
printf("{\"type\":\"tunnel_status\",\"active\":%s,\"timestamp\":%ld}\n",
tunnel_active ? "true" : "false", time(NULL));
fflush(stdout);
} else if (strcmp(buffer, "close") == 0 || strcmp(buffer, "quit") == 0 || strcmp(buffer, "exit") == 0) {
// Send tunnel_close message before exiting
if (current_ssl && active_tunnel) {
printf("{\"type\":\"sending_tunnel_close\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
}
printf("{\"type\":\"script_ending\",\"reason\":\"user_request\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
}
// Handle WebSocket data (messages from server)
if (ssl_fd >= 0 && FD_ISSET(ssl_fd, &readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Use SSL to read WebSocket data
int bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used,
sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read > 0) {
frame_buffer_used += bytes_read;
if (config->debug) {
printf("[DEBUG - Script] Accumulated %d bytes from WebSocket\n", frame_buffer_used);
fflush(stdout);
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer for processing
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
// Determine channel and forward message
const char *channel = "control";
if (strstr(buffer, "\"type\":\"tunnel_data\"") || strstr(buffer, "tunnel_data") ||
strstr(buffer, "\"type\":\"tunnel_response\"") || strstr(buffer, "tunnel_response")) {
channel = "data";
}
// In script mode, only print control channel messages (not data channel)
if (strcmp(channel, "control") == 0) {
printf("{\"type\":\"message_received\",\"channel\":\"%s\",\"message\":\"%s\",\"timestamp\":%ld}\n",
channel, buffer, time(NULL));
fflush(stdout);
}
// Handle all message types
if (strstr(buffer, "tunnel_request")) {
printf("{\"type\":\"tunnel_request_received\",\"message\":\"Server requested tunnel\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
} else if (strstr(buffer, "tunnel_ack")) {
printf("{\"type\":\"tunnel_ack_received\",\"message\":\"Tunnel acknowledged by server\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
} else if (strstr(buffer, "tunnel_data")) {
// In script mode, suppress data channel message notifications
// Extract request_id and data for processing (but don't print)
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
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_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_response")) {
// In script mode, suppress data channel message notifications
// Extract request_id and data for processing (but don't print)
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
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_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_close")) {
printf("{\"type\":\"tunnel_close_received\",\"message\":\"Server closed tunnel\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
// Extract request_id and handle tunnel close
char *id_start = strstr(buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
handle_tunnel_close(current_ssl, id_start, config->debug);
}
}
}
}
} else if (strstr(buffer, "tunnel_error")) {
printf("{\"type\":\"tunnel_error_received\",\"message\":\"Tunnel error from server\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
} else {
printf("{\"type\":\"unknown_message_received\",\"message\":\"%s\",\"timestamp\":%ld}\n", buffer, time(NULL));
fflush(stdout);
}
}
} else if (frame_type == 0x88) { // Close frame
printf("{\"type\":\"websocket_close\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
} else if (frame_type == 0x89) { // Ping frame
printf("{\"type\":\"ping_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
// Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) {
printf("{\"type\":\"pong_send_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
} else if (frame_type == 0x8A) { // Pong frame
printf("{\"type\":\"pong_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
}
} else if (bytes_read == 0) {
// Connection closed
printf("{\"type\":\"websocket_connection_closed\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
} else {
// Error
printf("{\"type\":\"websocket_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
}
}
}
// Small delay to prevent busy looping
usleep(25000); // 25ms
}
// Cleanup
printf("{\"type\":\"script_ended\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
return 0;
}
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) {
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);
}
// Daemon mode main loop - accept connections and establish tunnels
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, establishing tunnel...\n", local_port);
fflush(stdout);
}
// Now establish the tunnel for this connection
// In daemon mode, use port 0 to let setup_tunnel find an available port for WebSocket setup,
// but we'll use our already accepted socket for the local connection
int tunnel_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, 0, config->debug, 0, config->tunnel_host);
if (tunnel_sock < 0) {
fprintf(stderr, "Failed to establish tunnel for connection\n");
close(accepted_sock);
continue; // Don't exit, keep listening for new connections
}
// Close the dummy listening socket created by setup_tunnel
close(tunnel_sock);
// Print tunnel information (unless silent mode)
if (config->mode != MODE_SILENT) {
printf("\n");
printf("========================================\n");
printf(" WEBSSH TUNNEL READY\n");
printf("========================================\n");
printf("Tunnel established successfully!\n");
printf("Local port: %d\n", local_port);
printf("Target: %s@%s\n", client_id, wssshd_host);
printf("\n");
printf("Connect manually using one of these commands:\n");
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
// Set the accepted socket with mutex protection
pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = accepted_sock;
// Send any buffered data to the client immediately
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
if (config->debug) {
printf("[DEBUG - Tunnel] Sending %zu bytes of buffered server response to client\n", active_tunnel->incoming_buffer->used);
fflush(stdout);
}
ssize_t sent = send(accepted_sock, active_tunnel->incoming_buffer->buffer, active_tunnel->incoming_buffer->used, 0);
if (sent > 0) {
frame_buffer_consume(active_tunnel->incoming_buffer, sent);
if (config->debug) {
printf("[DEBUG] Sent %zd bytes of buffered server response to client\n", sent);
fflush(stdout);
}
}
}
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Local connection accepted! Starting data forwarding...\n");
fflush(stdout);
}
// Get initial SSL connection for thread
pthread_mutex_lock(&tunnel_mutex);
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
pthread_mutex_unlock(&tunnel_mutex);
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (!thread_args) {
perror("Memory allocation failed for thread args");
close(active_tunnel->local_sock);
free(active_tunnel);
active_tunnel = NULL;
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
thread_args->ssl = current_ssl;
thread_args->tunnel = active_tunnel;
thread_args->debug = config->debug;
pthread_t thread;
pthread_create(&thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(thread);
// Main tunnel loop - handle WebSocket messages
char buffer[BUFFER_SIZE];
int bytes_read;
fd_set tunnel_readfds;
struct timeval tunnel_tv;
int tunnel_broken = 0;
// Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0;
while (1) {
// Get SSL fd with mutex protection
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
if (active_tunnel && active_tunnel->broken) {
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
} else {
// normal closure
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel is no longer active, exiting main loop\n");
fflush(stdout);
}
break;
}
}
int ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
// Check if local socket is still valid
if (active_tunnel->local_sock < 0) {
if (config->debug) {
printf("[DEBUG - Tunnel] Local socket is invalid, tunnel broken\n");
fflush(stdout);
}
active_tunnel->broken = 1;
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to invalid local socket...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
}
// Check if the local socket connection is broken
char test_buf[1];
int result = recv(active_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE || errno == EBADF))) {
if (config->debug) {
printf("[DEBUG - Tunnel] Local socket connection is broken (errno=%d), sending tunnel_close\n", errno);
fflush(stdout);
}
active_tunnel->broken = 1;
// Send tunnel_close notification
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on SSL socket with timeout
FD_ZERO(&tunnel_readfds);
FD_SET(ssl_fd, &tunnel_readfds);
tunnel_tv.tv_sec = 0;
tunnel_tv.tv_usec = 50000; // 50ms timeout
int retval = select(ssl_fd + 1, &tunnel_readfds, NULL, NULL, &tunnel_tv);
if (retval == -1) {
if (config->debug) {
perror("[DEBUG - WebSockets] select on SSL fd failed");
fflush(stdout);
}
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to select failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel became inactive during timeout, exiting\n");
fflush(stdout);
}
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
continue;
}
// Read more data if we don't have a complete frame
if (FD_ISSET(ssl_fd, &tunnel_readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Validate SSL connection state
if (SSL_get_shutdown(current_ssl) & SSL_RECEIVED_SHUTDOWN) {
if (config->debug) {
printf("[DEBUG - WebSockets] SSL connection has received shutdown\n");
fflush(stdout);
}
cleanup_tunnel(config->debug);
break;
}
// Set up timeout for SSL read
fd_set readfds_timeout;
struct timeval tv_timeout;
int sock_fd = SSL_get_fd(current_ssl);
FD_ZERO(&readfds_timeout);
FD_SET(sock_fd, &readfds_timeout);
tv_timeout.tv_sec = 5;
tv_timeout.tv_usec = 0;
int select_result = select(sock_fd + 1, &readfds_timeout, NULL, NULL, &tv_timeout);
if (select_result == -1) {
if (config->debug) {
perror("[DEBUG - WebSockets] select failed");
fflush(stdout);
}
cleanup_tunnel(config->debug);
break;
} else if (select_result == 0) {
if (config->debug) {
printf("[DEBUG - WebSockets] SSL read timeout\n");
fflush(stdout);
}
continue;
}
bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read <= 0) {
if (bytes_read < 0) {
int ssl_error = SSL_get_error(current_ssl, bytes_read);
if (config->debug) {
printf("[DEBUG - WebSockets] SSL read error: %d\n", ssl_error);
fflush(stdout);
}
// Handle transient SSL errors
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
if (config->debug) {
printf("[DEBUG - WebSockets] Transient SSL error, retrying...\n");
fflush(stdout);
}
usleep(10000);
continue;
}
// Print SSL error details
char error_buf[256];
ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf));
if (config->debug) {
printf("[DEBUG - WebSockets] SSL error details: %s\n", error_buf);
fflush(stdout);
}
fprintf(stderr, "SSL read error (%d): %s\n", ssl_error, error_buf);
} else {
if (config->debug) {
printf("[DEBUG - WebSockets] Connection closed by server (EOF)\n");
fflush(stdout);
}
}
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket connection lost, attempting reconnection...\n");
fflush(stdout);
}
// Attempt reconnection
int reconnect_attempts = 0;
int max_reconnect_attempts = 3;
int reconnected = 0;
while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout);
}
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
if (reconnect_websocket(active_tunnel, wssshd_host, wssshd_port, client_id, active_tunnel->request_id, config->debug) == 0) {
reconnected = 1;
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection successful, continuing tunnel\n");
fflush(stdout);
}
// Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
}
pthread_mutex_unlock(&tunnel_mutex);
if (!reconnected) {
reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) {
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection failed, waiting 1 second...\n");
fflush(stdout);
}
sleep(1);
}
}
}
if (!reconnected) {
if (config->debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n");
fflush(stdout);
}
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to connection failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
}
// Skip processing this iteration since we just reconnected
continue;
}
frame_buffer_used += bytes_read;
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]);
fflush(stdout);
}
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x88) { // Close frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received close frame from server\n");
fflush(stdout);
}
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to server close frame...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
} else if (frame_type == 0x89) { // Ping frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received ping frame, sending pong\n");
fflush(stdout);
}
// Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) {
if (config->debug) {
printf("[DEBUG - WebSockets] Failed to send pong frame\n");
fflush(stdout);
}
}
} else if (frame_type == 0x8A) { // Pong frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received pong frame\n");
fflush(stdout);
}
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
} else {
fprintf(stderr, "Payload too large for processing buffer\n");
frame_buffer_used = 0;
continue;
}
// Check if this is a data message to suppress verbose logging
int is_data_message = (strstr(buffer, "\"type\": \"tunnel_data\"") != NULL ||
strstr(buffer, "\"type\": \"tunnel_response\"") != NULL);
if (config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// Handle message
if (config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
fflush(stdout);
}
// Handle tunnel messages
if (strstr(buffer, "tunnel_data") || strstr(buffer, "tunnel_response")) {
if (config->debug) {
// Suppress tunnel_data debug messages in debug mode
if (!strstr(buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] Received tunnel_response message\n");
fflush(stdout);
}
}
// Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
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_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_close")) {
if (config->debug) {
printf("[DEBUG - Tunnel] Received tunnel_close message\n");
fflush(stdout);
}
char *id_start = strstr(buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
handle_tunnel_close(current_ssl, id_start, config->debug);
}
}
}
}
} else {
if (config->debug) {
printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer);
fflush(stdout);
}
}
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
} else {
// Frame not complete yet, continue reading
continue;
}
}
}
cleanup_and_exit:
// Cleanup section
if (config->debug) {
printf("[DEBUG - Tunnel] Performing cleanup and exiting\n");
fflush(stdout);
}
// Cleanup
if (active_tunnel) {
if (active_tunnel->local_sock >= 0) {
close(active_tunnel->local_sock);
}
if (active_tunnel->ssl) {
SSL_free(active_tunnel->ssl);
}
free(active_tunnel);
active_tunnel = NULL;
}
free(config->local_port);
free(config->tunnel);
free(config->tunnel_control);
free(config->service);
// Set to NULL to prevent double-free in main()
config->local_port = 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);
if (config->debug) {
printf("[DEBUG - Tunnel] Cleanup complete, exiting with code %d\n", tunnel_broken ? 1 : 0);
fflush(stdout);
}
// In daemon mode, continue to accept new connections
// Continue the while(1) loop for next connection
}
// This should never be reached, but just in case
return 0;
}
/*
* WebSocket SSH Tunnel (wsssht) - Mode Functions
* Mode-specific functions for wsssht
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MODES_H
#define MODES_H
#include "../wssshlib.h"
// Function declarations for mode-specific functions
int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port);
int run_script_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port);
int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port);
#endif // MODES_H
\ No newline at end of file
/*
* WebSocket SSH Tunnel (wsssht) - Thread Functions Implementation
* Thread-related functions for wsssht
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <getopt.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/select.h>
#include <time.h>
#include <errno.h>
#include "../wssshlib.h"
#include "../websocket.h"
#include "../wssh_ssl.h"
#include "../tunnel.h"
#include "wsssht.h"
#include "threads.h"
void *run_tunnel_thread(void *arg) {
tunnel_thread_args_t *args = (tunnel_thread_args_t *)arg;
// Establish the tunnel for this connection
int tunnel_sock = setup_tunnel(args->wssshd_host, args->wssshd_port, args->client_id, 0, args->config->debug, 0, args->tunnel_host);
if (tunnel_sock < 0) {
fprintf(stderr, "Failed to establish tunnel for connection\n");
close(args->accepted_sock);
free(args);
return NULL;
}
// Close the dummy listening socket created by setup_tunnel
close(tunnel_sock);
// Print tunnel information (unless silent mode)
if (args->config->mode != MODE_SILENT) {
printf("\n");
printf("========================================\n");
printf(" WEBSSH TUNNEL READY\n");
printf("========================================\n");
printf("Tunnel established successfully!\n");
printf("Local port: %d\n", atoi(args->config->local_port));
printf("Target: %s@%s\n", args->client_id, args->wssshd_host);
printf("\n");
printf("Connect manually using one of these commands:\n");
printf("\n");
printf(" Telnet:\n");
printf(" telnet localhost %d\n", atoi(args->config->local_port));
printf("\n");
printf(" Netcat:\n");
printf(" nc localhost %d\n", atoi(args->config->local_port));
printf("\n");
printf(" SSH (if connecting to SSH server):\n");
printf(" ssh -p %d user@localhost\n", atoi(args->config->local_port));
printf("\n");
printf(" SCP (if connecting to SSH server):\n");
printf(" scp -P %d user@localhost:/remote/path ./local/path\n", atoi(args->config->local_port));
printf("\n");
printf(" Any TCP client:\n");
printf(" Connect to localhost:%d\n", atoi(args->config->local_port));
printf("\n");
printf("Press Ctrl+C to close the tunnel and exit.\n");
printf("========================================\n");
printf("\n");
}
// Set the accepted socket with mutex protection
pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = args->accepted_sock;
// Send any buffered data to the client immediately
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending %zu bytes of buffered server response to client\n", active_tunnel->incoming_buffer->used);
fflush(stdout);
}
ssize_t sent = send(args->accepted_sock, active_tunnel->incoming_buffer->buffer, active_tunnel->incoming_buffer->used, 0);
if (sent > 0) {
frame_buffer_consume(active_tunnel->incoming_buffer, sent);
if (args->config->debug) {
printf("[DEBUG] Sent %zd bytes of buffered server response to client\n", sent);
fflush(stdout);
}
}
}
pthread_mutex_unlock(&tunnel_mutex);
if (args->config->debug) {
printf("[DEBUG - Tunnel] Local connection accepted! Starting data forwarding...\n");
fflush(stdout);
}
// Get initial SSL connection for thread
pthread_mutex_lock(&tunnel_mutex);
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
pthread_mutex_unlock(&tunnel_mutex);
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (!thread_args) {
perror("Memory allocation failed for thread args");
close(active_tunnel->local_sock);
free(active_tunnel);
active_tunnel = NULL;
free(args);
return NULL;
}
thread_args->ssl = current_ssl;
thread_args->tunnel = active_tunnel;
thread_args->debug = args->config->debug;
pthread_t forwarding_thread;
pthread_create(&forwarding_thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(forwarding_thread);
// Main tunnel loop - handle WebSocket messages
char buffer[BUFFER_SIZE];
int bytes_read;
fd_set tunnel_readfds;
struct timeval tunnel_tv;
// Frame accumulation buffer for handling partial WebSocket frames
char frame_buffer[BUFFER_SIZE * 4];
int frame_buffer_used = 0;
while (1) {
// Get SSL fd with mutex protection
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
if (active_tunnel && active_tunnel->broken) {
pthread_mutex_unlock(&tunnel_mutex);
goto thread_cleanup;
} else {
// normal closure
pthread_mutex_unlock(&tunnel_mutex);
if (args->config->debug) {
printf("[DEBUG - Tunnel] Tunnel is no longer active, exiting main loop\n");
fflush(stdout);
}
break;
}
}
int ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
// Check if local socket is still valid
if (active_tunnel->local_sock < 0) {
if (args->config->debug) {
printf("[DEBUG - Tunnel] Local socket is invalid, tunnel broken\n");
fflush(stdout);
}
active_tunnel->broken = 1;
// Send tunnel_close notification
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to invalid local socket...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto thread_cleanup;
}
// Check if the local socket connection is broken
char test_buf[1];
int result = recv(active_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE || errno == EBADF))) {
if (args->config->debug) {
printf("[DEBUG - Tunnel] Local socket connection is broken (errno=%d), sending tunnel_close\n", errno);
fflush(stdout);
}
active_tunnel->broken = 1;
// Send tunnel_close notification
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto thread_cleanup;
}
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on SSL socket with timeout
FD_ZERO(&tunnel_readfds);
FD_SET(ssl_fd, &tunnel_readfds);
tunnel_tv.tv_sec = 0;
tunnel_tv.tv_usec = 50000; // 50ms timeout
int retval = select(ssl_fd + 1, &tunnel_readfds, NULL, NULL, &tunnel_tv);
if (retval == -1) {
if (args->config->debug) {
perror("[DEBUG - WebSockets] select on SSL fd failed");
fflush(stdout);
}
// Send tunnel_close notification
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to select failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
goto thread_cleanup;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
if (args->config->debug) {
printf("[DEBUG - Tunnel] Tunnel became inactive during timeout, exiting\n");
fflush(stdout);
}
goto thread_cleanup;
}
pthread_mutex_unlock(&tunnel_mutex);
continue;
}
// Read more data if we don't have a complete frame
if (FD_ISSET(ssl_fd, &tunnel_readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Validate SSL connection state
if (SSL_get_shutdown(current_ssl) & SSL_RECEIVED_SHUTDOWN) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] SSL connection has received shutdown\n");
fflush(stdout);
}
cleanup_tunnel(args->config->debug);
break;
}
// Set up timeout for SSL read
fd_set readfds_timeout;
struct timeval tv_timeout;
int sock_fd = SSL_get_fd(current_ssl);
FD_ZERO(&readfds_timeout);
FD_SET(sock_fd, &readfds_timeout);
tv_timeout.tv_sec = 5;
tv_timeout.tv_usec = 0;
int select_result = select(sock_fd + 1, &readfds_timeout, NULL, NULL, &tv_timeout);
if (select_result == -1) {
if (args->config->debug) {
perror("[DEBUG - WebSockets] select failed");
fflush(stdout);
}
cleanup_tunnel(args->config->debug);
break;
} else if (select_result == 0) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] SSL read timeout\n");
fflush(stdout);
}
continue;
}
bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read <= 0) {
if (bytes_read < 0) {
int ssl_error = SSL_get_error(current_ssl, bytes_read);
if (args->config->debug) {
printf("[DEBUG - WebSockets] SSL read error: %d\n", ssl_error);
fflush(stdout);
}
// Handle transient SSL errors
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] Transient SSL error, retrying...\n");
fflush(stdout);
}
usleep(10000);
continue;
}
// Print SSL error details
char error_buf[256];
ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf));
if (args->config->debug) {
printf("[DEBUG - WebSockets] SSL error details: %s\n", error_buf);
fflush(stdout);
}
fprintf(stderr, "SSL read error (%d): %s\n", ssl_error, error_buf);
} else {
if (args->config->debug) {
printf("[DEBUG - WebSockets] Connection closed by server (EOF)\n");
fflush(stdout);
}
}
if (args->config->debug) {
printf("[DEBUG - WebSockets] WebSocket connection lost, attempting reconnection...\n");
fflush(stdout);
}
// Attempt reconnection
int reconnect_attempts = 0;
int max_reconnect_attempts = 3;
int reconnected = 0;
while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout);
}
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
if (reconnect_websocket(active_tunnel, args->wssshd_host, args->wssshd_port, args->client_id, active_tunnel->request_id, args->config->debug) == 0) {
reconnected = 1;
if (args->config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection successful, continuing tunnel\n");
fflush(stdout);
}
// Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
}
pthread_mutex_unlock(&tunnel_mutex);
if (!reconnected) {
reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection failed, waiting 1 second...\n");
fflush(stdout);
}
sleep(1);
}
}
}
if (!reconnected) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n");
fflush(stdout);
}
// Send tunnel_close notification
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to connection failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
goto thread_cleanup;
}
// Skip processing this iteration since we just reconnected
continue;
}
frame_buffer_used += bytes_read;
if (args->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]);
fflush(stdout);
}
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x88) { // Close frame
if (args->config->debug) {
printf("[DEBUG - WebSockets] Received close frame from server\n");
fflush(stdout);
}
// Send tunnel_close notification
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to server close frame...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
goto thread_cleanup;
} else if (frame_type == 0x89) { // Ping frame
if (args->config->debug) {
printf("[DEBUG - WebSockets] Received ping frame, sending pong\n");
fflush(stdout);
}
// Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] Failed to send pong frame\n");
fflush(stdout);
}
}
} else if (frame_type == 0x8A) { // Pong frame
if (args->config->debug) {
printf("[DEBUG - WebSockets] Received pong frame\n");
fflush(stdout);
}
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
} else {
fprintf(stderr, "Payload too large for processing buffer\n");
frame_buffer_used = 0;
continue;
}
// Check if this is a data message to suppress verbose logging
int is_data_message = (strstr(buffer, "\"type\":\"tunnel_data\"") != NULL ||
strstr(buffer, "\"type\":\"tunnel_response\"") != NULL);
if (args->config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// Handle message
if (args->config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
fflush(stdout);
}
// Handle tunnel messages
if (strstr(buffer, "tunnel_data") || strstr(buffer, "tunnel_response")) {
if (args->config->debug) {
// Suppress tunnel_data debug messages in debug mode
if (!strstr(buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] Received tunnel_response message\n");
fflush(stdout);
}
}
// Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
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_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, args->config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_close")) {
if (args->config->debug) {
printf("[DEBUG - Tunnel] Received tunnel_close message\n");
fflush(stdout);
}
char *id_start = strstr(buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
handle_tunnel_close(current_ssl, id_start, args->config->debug);
}
}
}
}
} else {
if (args->config->debug) {
printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer);
fflush(stdout);
}
}
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
} else {
// Frame not complete yet, continue reading
continue;
}
}
}
thread_cleanup:
// Cleanup section
if (args->config->debug) {
printf("[DEBUG - Tunnel] Performing cleanup and exiting\n");
fflush(stdout);
}
// Cleanup
if (active_tunnel) {
if (active_tunnel->local_sock >= 0) {
close(active_tunnel->local_sock);
}
if (active_tunnel->ssl) {
SSL_free(active_tunnel->ssl);
}
free(active_tunnel);
active_tunnel = NULL;
}
free(args);
return NULL;
}
\ No newline at end of file
/*
* WebSocket SSH Tunnel (wsssht) - Thread Functions
* Thread-related functions for wsssht
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef THREADS_H
#define THREADS_H
#include "wsssht.h"
// Function declarations for thread functions
void *run_tunnel_thread(void *arg);
#endif // THREADS_H
\ No newline at end of file
/*
* WebSocket SSH Tunnel (wsssht) - Utility Functions Implementation
* Utility functions for wsssht
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "../wssshlib.h"
#include "utils.h"
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options] [service://]clientid[@wssshd-host][:wssshd-port]\n", program_name);
fprintf(stderr, "WebSocket SSH Tunnel - Setup WebSocket tunnels for manual connections\n\n");
fprintf(stderr, "Protect the dolls!\n\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " --config FILE Use custom config file (takes precedence over default)\n");
fprintf(stderr, " --clientid ID Client ID for the tunnel (default: from config)\n");
fprintf(stderr, " --tunnel-port PORT Local tunnel port (default: auto)\n");
fprintf(stderr, " --tunnel-host HOST Local IP address to bind tunnel to (default: 127.0.0.1)\n");
fprintf(stderr, " --wssshd-host HOST wssshd server hostname (required if not in config)\n");
fprintf(stderr, " --wssshd-port PORT wssshd server port (default: 9898)\n");
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 5)\n");
fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --tunnel TRANSPORT Transport for data channel (comma-separated or 'any', or 'websocket' default: any)\n");
fprintf(stderr, " --tunnel-control TYPES Transport types for control channel (comma-separated or 'any', default: any)\n");
fprintf(stderr, " --service SERVICE Service type (default: ssh)\n");
fprintf(stderr, " --mode MODE Operating mode: interactive, silent, bridge, script (default: interactive)\n");
fprintf(stderr, " --silent Shortcut for --mode silent\n");
fprintf(stderr, " --bridge Shortcut for --mode bridge\n");
fprintf(stderr, " --script Shortcut for --mode script\n");
fprintf(stderr, " --daemon Enable daemon mode for lazy initialization\n");
fprintf(stderr, " --help Show this help\n");
fprintf(stderr, "\nExamples:\n");
fprintf(stderr, " %s --clientid myclient --wssshd-host mbetter.nexlab.net\n", program_name);
fprintf(stderr, " %s myclient@mbetter.nexlab.net:9898\n", program_name);
fprintf(stderr, " %s ssh://myclient@mbetter.nexlab.net\n", program_name);
fprintf(stderr, " %s --tunnel websocket myclient@mbetter.nexlab.net\n", program_name);
fprintf(stderr, " %s --silent myclient@mbetter.nexlab.net\n", program_name);
fprintf(stderr, " %s --daemon --clientid myclient --wssshd-host mbetter.nexlab.net\n", program_name);
fprintf(stderr, "\nDonations:\n");
fprintf(stderr, " BTC: bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\n");
fprintf(stderr, " ETH: 0xdA6dAb526515b5cb556d20269207D43fcc760E51\n");
}
int parse_connection_string(const char *conn_str, char **service, char **client_id, char **wssshd_host, int *wssshd_port) {
char *str = strdup(conn_str);
if (!str) return 0;
char *working_str = str; // Keep track of what to free
// Check for service prefix (e.g., "ssh://client@host:port")
char *service_end = strstr(str, "://");
if (service_end) {
*service_end = '\0';
*service = strdup(str);
working_str = service_end + 3; // Point to part after "://"
}
// Find @ separator for client@host
char *at_pos = strchr(working_str, '@');
if (!at_pos) {
// No @ found, treat whole string as client_id
*client_id = strdup(working_str);
free(str);
return 1;
}
// Split client_id and host part
*at_pos = '\0';
*client_id = strdup(working_str);
// Parse host and port
char *host_part = at_pos + 1;
char *colon_pos = strchr(host_part, ':');
if (colon_pos) {
// Has port
*colon_pos = '\0';
*wssshd_host = strdup(host_part);
*wssshd_port = atoi(colon_pos + 1);
} else {
// No port
*wssshd_host = strdup(host_part);
}
free(str);
return 1;
}
int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_argc, char ***remaining_argv) {
// Parse wsssht options and optional connection string
int target_start = -1;
char *custom_config = NULL;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) {
custom_config = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--clientid") == 0 && i + 1 < argc) {
if (config->client_id) free(config->client_id);
config->client_id = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel-port") == 0 && i + 1 < argc) {
config->local_port = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel-host") == 0 && i + 1 < argc) {
if (config->tunnel_host) free(config->tunnel_host);
config->tunnel_host = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--wssshd-host") == 0 && i + 1 < argc) {
if (config->wssshd_host) free(config->wssshd_host);
config->wssshd_host = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--wssshd-port") == 0 && i + 1 < argc) {
config->wssshd_port = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
config->interval = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel") == 0 && i + 1 < argc) {
if (config->tunnel) free(config->tunnel);
config->tunnel = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel-control") == 0 && i + 1 < argc) {
if (config->tunnel_control) free(config->tunnel_control);
config->tunnel_control = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--service") == 0 && i + 1 < argc) {
if (config->service) free(config->service);
config->service = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--debug") == 0) {
config->debug = 1;
} else if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) {
if (strcmp(argv[i + 1], "interactive") == 0) {
config->mode = MODE_INTERACTIVE;
} else if (strcmp(argv[i + 1], "silent") == 0) {
config->mode = MODE_SILENT;
} else if (strcmp(argv[i + 1], "bridge") == 0) {
config->mode = MODE_BRIDGE;
} else if (strcmp(argv[i + 1], "script") == 0) {
config->mode = MODE_SCRIPT;
} else {
fprintf(stderr, "Error: Invalid mode: %s\n", argv[i + 1]);
print_usage(argv[0]);
return 0;
}
i++; // Skip the argument
} else if (strcmp(argv[i], "--daemon") == 0) {
config->daemon = 1;
} else if (strcmp(argv[i], "--silent") == 0) {
config->mode = MODE_SILENT;
} else if (strcmp(argv[i], "--bridge") == 0) {
config->mode = MODE_BRIDGE;
} else if (strcmp(argv[i], "--script") == 0) {
config->mode = MODE_SCRIPT;
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_usage(argv[0]);
return 0;
} else if (argv[i][0] == '-') {
// Unknown option
fprintf(stderr, "Error: Unknown option: %s\n", argv[i]);
print_usage(argv[0]);
return 0;
} else {
// Non-option argument - should be connection string
if (target_start == -1) {
target_start = i;
} else {
// Multiple non-option arguments not allowed
fprintf(stderr, "Error: Multiple connection strings not allowed\n");
print_usage(argv[0]);
return 0;
}
}
}
// Handle custom config file if specified
if (custom_config) {
// TODO: Implement custom config file loading
// For now, just validate the file exists
FILE *test = fopen(custom_config, "r");
if (!test) {
fprintf(stderr, "Error: Config file not found: %s\n", custom_config);
free(custom_config);
return 0;
}
fclose(test);
fprintf(stderr, "Warning: Custom config file support not yet implemented\n");
free(custom_config);
}
// Handle connection string if present
if (target_start != -1) {
char *service = NULL;
char *client_id = NULL;
char *wssshd_host = NULL;
int wssshd_port = 9898;
if (!parse_connection_string(argv[target_start], &service, &client_id, &wssshd_host, &wssshd_port)) {
fprintf(stderr, "Error: Invalid connection string format\n");
free(service);
free(client_id);
free(wssshd_host);
return 0;
}
// Set values from connection string (these take precedence over config file)
if (service) {
if (config->service) free(config->service);
config->service = service;
}
if (client_id) {
if (config->client_id) free(config->client_id);
config->client_id = client_id;
}
if (wssshd_host) {
if (config->wssshd_host) free(config->wssshd_host);
config->wssshd_host = wssshd_host;
}
if (wssshd_port != 9898) {
config->wssshd_port = wssshd_port;
}
}
// Return remaining arguments (should be none)
if (target_start != -1) {
*remaining_argc = argc - target_start - 1;
*remaining_argv = &argv[target_start + 1];
} else {
*remaining_argc = 0;
*remaining_argv = NULL;
}
return 1;
}
\ No newline at end of file
/*
* WebSocket SSH Tunnel (wsssht) - Utility Functions
* Utility functions for wsssht
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef UTILS_H
#define UTILS_H
#include "../wssshlib.h"
// Function declarations for utility functions
void print_usage(const char *program_name);
int parse_connection_string(const char *conn_str, char **service, char **client_id, char **wssshd_host, int *wssshd_port);
int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_argc, char ***remaining_argv);
#endif // UTILS_H
\ No newline at end of file
/*
* WebSocket SSH Tunnel (wsssht) - C Implementation
* WebSocket tunnel setup tool for manual connections.
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef WSSSHT_H
#define WSSSHT_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <getopt.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/select.h>
#include "../wssshlib.h"
#include "../websocket.h"
#include "../wssh_ssl.h"
#include "../tunnel.h"
// Thread arguments structure for concurrent tunnel handling
typedef struct {
int accepted_sock;
wsssh_config_t *config;
const char *client_id;
const char *wssshd_host;
int wssshd_port;
const char *tunnel_host;
} tunnel_thread_args_t;
// Function declarations
void print_usage(const char *program_name);
int parse_connection_string(const char *conn_str, char **service, char **client_id, char **wssshd_host, int *wssshd_port);
int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_argc, char ***remaining_argv);
int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port);
int run_script_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port);
int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port);
void *run_tunnel_thread(void *arg);
#endif // WSSSHT_H
\ No newline at end of file
No preview for this file type
/* /*
* WebSocket SSH Tunnel (wsssht) - C Implementation * WebSocket SSH Tunnel (wsssht) - Main Program
* WebSocket tunnel setup tool for manual connections. * WebSocket tunnel setup tool for manual connections.
* *
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me * Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
...@@ -37,1978 +37,10 @@ ...@@ -37,1978 +37,10 @@
#include "wssh_ssl.h" #include "wssh_ssl.h"
#include "tunnel.h" #include "tunnel.h"
#include "libwsssht/wsssht.h"
#include "libwsssht/utils.h"
#include "libwsssht/modes.h"
void print_usage(const char *program_name) { #include "libwsssht/threads.h"
fprintf(stderr, "Usage: %s [options] [service://]clientid[@wssshd-host][:wssshd-port]\n", program_name);
fprintf(stderr, "WebSocket SSH Tunnel - Setup WebSocket tunnels for manual connections\n\n");
fprintf(stderr, "Protect the dolls!\n\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " --config FILE Use custom config file (takes precedence over default)\n");
fprintf(stderr, " --clientid ID Client ID for the tunnel (default: from config)\n");
fprintf(stderr, " --tunnel-port PORT Local tunnel port (default: auto)\n");
fprintf(stderr, " --tunnel-host HOST Local IP address to bind tunnel to (default: 127.0.0.1)\n");
fprintf(stderr, " --wssshd-host HOST wssshd server hostname (required if not in config)\n");
fprintf(stderr, " --wssshd-port PORT wssshd server port (default: 9898)\n");
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 5)\n");
fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --tunnel TRANSPORT Transport for data channel (comma-separated or 'any', or 'websocket' default: any)\n");
fprintf(stderr, " --tunnel-control TYPES Transport types for control channel (comma-separated or 'any', default: any)\n");
fprintf(stderr, " --service SERVICE Service type (default: ssh)\n");
fprintf(stderr, " --mode MODE Operating mode: interactive, silent, bridge, script (default: interactive)\n");
fprintf(stderr, " --silent Shortcut for --mode silent\n");
fprintf(stderr, " --bridge Shortcut for --mode bridge\n");
fprintf(stderr, " --script Shortcut for --mode script\n");
fprintf(stderr, " --daemon Enable daemon mode for lazy initialization\n");
fprintf(stderr, " --help Show this help\n");
fprintf(stderr, "\nExamples:\n");
fprintf(stderr, " %s --clientid myclient --wssshd-host mbetter.nexlab.net\n", program_name);
fprintf(stderr, " %s myclient@mbetter.nexlab.net:9898\n", program_name);
fprintf(stderr, " %s ssh://myclient@mbetter.nexlab.net\n", program_name);
fprintf(stderr, " %s --tunnel websocket myclient@mbetter.nexlab.net\n", program_name);
fprintf(stderr, " %s --silent myclient@mbetter.nexlab.net\n", program_name);
fprintf(stderr, " %s --daemon --clientid myclient --wssshd-host mbetter.nexlab.net\n", program_name);
fprintf(stderr, "\nDonations:\n");
fprintf(stderr, " BTC: bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\n");
fprintf(stderr, " ETH: 0xdA6dAb526515b5cb556d20269207D43fcc760E51\n");
}
int parse_connection_string(const char *conn_str, char **service, char **client_id, char **wssshd_host, int *wssshd_port) {
char *str = strdup(conn_str);
if (!str) return 0;
char *working_str = str; // Keep track of what to free
// Check for service prefix (e.g., "ssh://client@host:port")
char *service_end = strstr(str, "://");
if (service_end) {
*service_end = '\0';
*service = strdup(str);
working_str = service_end + 3; // Point to part after "://"
}
// Find @ separator for client@host
char *at_pos = strchr(working_str, '@');
if (!at_pos) {
// No @ found, treat whole string as client_id
*client_id = strdup(working_str);
free(str);
return 1;
}
// Split client_id and host part
*at_pos = '\0';
*client_id = strdup(working_str);
// Parse host and port
char *host_part = at_pos + 1;
char *colon_pos = strchr(host_part, ':');
if (colon_pos) {
// Has port
*colon_pos = '\0';
*wssshd_host = strdup(host_part);
*wssshd_port = atoi(colon_pos + 1);
} else {
// No port
*wssshd_host = strdup(host_part);
}
free(str);
return 1;
}
int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_argc, char ***remaining_argv) {
// Parse wsssht options and optional connection string
int target_start = -1;
char *custom_config = NULL;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) {
custom_config = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--clientid") == 0 && i + 1 < argc) {
if (config->client_id) free(config->client_id);
config->client_id = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel-port") == 0 && i + 1 < argc) {
config->local_port = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel-host") == 0 && i + 1 < argc) {
if (config->tunnel_host) free(config->tunnel_host);
config->tunnel_host = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--wssshd-host") == 0 && i + 1 < argc) {
if (config->wssshd_host) free(config->wssshd_host);
config->wssshd_host = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--wssshd-port") == 0 && i + 1 < argc) {
config->wssshd_port = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
config->interval = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel") == 0 && i + 1 < argc) {
if (config->tunnel) free(config->tunnel);
config->tunnel = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--tunnel-control") == 0 && i + 1 < argc) {
if (config->tunnel_control) free(config->tunnel_control);
config->tunnel_control = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--service") == 0 && i + 1 < argc) {
if (config->service) free(config->service);
config->service = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--debug") == 0) {
config->debug = 1;
} else if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) {
if (strcmp(argv[i + 1], "interactive") == 0) {
config->mode = MODE_INTERACTIVE;
} else if (strcmp(argv[i + 1], "silent") == 0) {
config->mode = MODE_SILENT;
} else if (strcmp(argv[i + 1], "bridge") == 0) {
config->mode = MODE_BRIDGE;
} else if (strcmp(argv[i + 1], "script") == 0) {
config->mode = MODE_SCRIPT;
} else {
fprintf(stderr, "Error: Invalid mode: %s\n", argv[i + 1]);
print_usage(argv[0]);
return 0;
}
i++; // Skip the argument
} else if (strcmp(argv[i], "--daemon") == 0) {
config->daemon = 1;
} else if (strcmp(argv[i], "--silent") == 0) {
config->mode = MODE_SILENT;
} else if (strcmp(argv[i], "--bridge") == 0) {
config->mode = MODE_BRIDGE;
} else if (strcmp(argv[i], "--script") == 0) {
config->mode = MODE_SCRIPT;
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_usage(argv[0]);
return 0;
} else if (argv[i][0] == '-') {
// Unknown option
fprintf(stderr, "Error: Unknown option: %s\n", argv[i]);
print_usage(argv[0]);
return 0;
} else {
// Non-option argument - should be connection string
if (target_start == -1) {
target_start = i;
} else {
// Multiple non-option arguments not allowed
fprintf(stderr, "Error: Multiple connection strings not allowed\n");
print_usage(argv[0]);
return 0;
}
}
}
// Handle custom config file if specified
if (custom_config) {
// TODO: Implement custom config file loading
// For now, just validate the file exists
FILE *test = fopen(custom_config, "r");
if (!test) {
fprintf(stderr, "Error: Config file not found: %s\n", custom_config);
free(custom_config);
return 0;
}
fclose(test);
fprintf(stderr, "Warning: Custom config file support not yet implemented\n");
free(custom_config);
}
// Handle connection string if present
if (target_start != -1) {
char *service = NULL;
char *client_id = NULL;
char *wssshd_host = NULL;
int wssshd_port = 9898;
if (!parse_connection_string(argv[target_start], &service, &client_id, &wssshd_host, &wssshd_port)) {
fprintf(stderr, "Error: Invalid connection string format\n");
free(service);
free(client_id);
free(wssshd_host);
return 0;
}
// Set values from connection string (these take precedence over config file)
if (service) {
if (config->service) free(config->service);
config->service = service;
}
if (client_id) {
if (config->client_id) free(config->client_id);
config->client_id = client_id;
}
if (wssshd_host) {
if (config->wssshd_host) free(config->wssshd_host);
config->wssshd_host = wssshd_host;
}
if (wssshd_port != 9898) {
config->wssshd_port = wssshd_port;
}
}
// Return remaining arguments (should be none)
if (target_start != -1) {
*remaining_argc = argc - target_start - 1;
*remaining_argv = &argv[target_start + 1];
} else {
*remaining_argc = 0;
*remaining_argv = NULL;
}
return 1;
}
int run_bridge_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) {
// Bridge mode: Pure transport layer - no tunnel setup, just WebSocket transport
if (config->debug) {
printf("[DEBUG] Starting bridge mode (pure transport) with client_id=%s, host=%s, port=%d\n",
client_id, wssshd_host, wssshd_port);
fflush(stdout);
}
// Send initial status
printf("{\"type\":\"bridge_started\",\"client_id\":\"%s\",\"host\":\"%s\",\"port\":%d,\"timestamp\":%ld}\n",
client_id, wssshd_host, wssshd_port, time(NULL));
fflush(stdout);
// Establish WebSocket connection (no tunnel setup)
SSL_CTX *ws_ctx = NULL;
int ws_sock = setup_websocket_connection(wssshd_host, wssshd_port, client_id, config->debug, &ws_ctx);
if (ws_sock < 0) {
printf("{\"type\":\"error\",\"message\":\"Failed to establish WebSocket connection\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
if (ws_ctx) SSL_CTX_free(ws_ctx);
return 1;
}
// Get the SSL connection for sending messages
SSL *ws_ssl = active_tunnel ? active_tunnel->ssl : NULL;
if (!ws_ssl) {
printf("{\"type\":\"error\",\"message\":\"Failed to get SSL connection\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
close(ws_sock);
if (ws_ctx) SSL_CTX_free(ws_ctx);
return 1;
}
// Send connection established message
printf("{\"type\":\"websocket_connected\",\"socket\":%d,\"timestamp\":%ld}\n", ws_sock, time(NULL));
fflush(stdout);
// Main bridge loop - pure transport layer
char buffer[BUFFER_SIZE];
fd_set readfds;
struct timeval tv;
// Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0;
printf("{\"type\":\"bridge_ready\",\"message\":\"Pure transport layer active\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
while (1) {
// Check for stdin input (messages to send to server)
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
FD_SET(ws_sock, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 50000; // 50ms timeout
int max_fd = (ws_sock > STDIN_FILENO) ? ws_sock : STDIN_FILENO;
int retval = select(max_fd + 1, &readfds, NULL, NULL, &tv);
if (retval > 0) {
// Handle stdin input (messages to send to server)
if (FD_ISSET(STDIN_FILENO, &readfds)) {
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
// EOF or error on stdin
printf("{\"type\":\"stdin_closed\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
// Remove trailing newline
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
len--;
}
if (len > 0) {
// Determine channel based on message content
const char *channel = "control";
if (strstr(buffer, "\"type\":\"tunnel_data\"") || strstr(buffer, "tunnel_data")) {
channel = "data";
}
// Send message to appropriate WebSocket channel using SSL
if (send_websocket_frame(ws_ssl, buffer)) {
printf("{\"type\":\"message_sent\",\"channel\":\"%s\",\"message\":\"%s\",\"timestamp\":%ld}\n",
channel, buffer, time(NULL));
fflush(stdout);
} else {
printf("{\"type\":\"send_error\",\"channel\":\"%s\",\"timestamp\":%ld}\n", channel, time(NULL));
fflush(stdout);
}
}
}
// Handle WebSocket data (messages from server)
if (FD_ISSET(ws_sock, &readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Use SSL to read WebSocket data
int bytes_read = SSL_read(ws_ssl, frame_buffer + frame_buffer_used,
sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read > 0) {
frame_buffer_used += bytes_read;
if (config->debug) {
printf("[DEBUG - Bridge] Accumulated %d bytes from WebSocket\n", frame_buffer_used);
fflush(stdout);
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer for processing
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
// Determine channel and forward message
const char *channel = "control";
if (strstr(buffer, "\"type\":\"tunnel_data\"") || strstr(buffer, "tunnel_data")) {
channel = "data";
}
printf("{\"type\":\"message_received\",\"channel\":\"%s\",\"message\":\"%s\",\"timestamp\":%ld}\n",
channel, buffer, time(NULL));
fflush(stdout);
}
} else if (frame_type == 0x88) { // Close frame
printf("{\"type\":\"websocket_close\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
} else if (frame_type == 0x89) { // Ping frame
printf("{\"type\":\"ping_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
// Send pong
if (!send_pong_frame(ws_ssl, payload, payload_len)) {
printf("{\"type\":\"pong_send_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
} else if (frame_type == 0x8A) { // Pong frame
printf("{\"type\":\"pong_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
}
} else if (bytes_read == 0) {
// Connection closed
printf("{\"type\":\"websocket_connection_closed\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
} else {
// Error
printf("{\"type\":\"websocket_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
}
}
}
// Small delay to prevent busy looping
usleep(25000); // 25ms
}
// Cleanup
close(ws_sock);
// Clean up SSL resources for bridge mode
if (active_tunnel && active_tunnel->ssl) {
SSL_free(active_tunnel->ssl);
active_tunnel->ssl = NULL;
}
if (ws_ctx) {
SSL_CTX_free(ws_ctx);
ws_ctx = NULL;
}
printf("{\"type\":\"bridge_ended\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
return 0;
}
int run_script_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) {
// Script mode: JSON protocol for scripting
if (config->debug) {
printf("[DEBUG] Starting script mode with client_id=%s, host=%s, port=%d\n",
client_id, wssshd_host, wssshd_port);
fflush(stdout);
}
// Send initial status for scripting
printf("{\"type\":\"script_started\",\"client_id\":\"%s\",\"host\":\"%s\",\"port\":%d,\"tunnel_host\":\"%s\",\"tunnel_port\":\"%s\",\"service\":\"%s\",\"timestamp\":%ld}\n",
client_id, wssshd_host, wssshd_port,
config->tunnel_host ? config->tunnel_host : "127.0.0.1",
config->local_port ? config->local_port : "auto",
config->service ? config->service : "ssh",
time(NULL));
fflush(stdout);
// Establish tunnel
int listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, config->local_port ? atoi(config->local_port) : find_available_port(),
config->debug, 0, config->tunnel_host);
if (listen_sock < 0) {
printf("{\"type\":\"script_error\",\"message\":\"Failed to establish tunnel\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
return 1;
}
// Send tunnel established message
printf("{\"type\":\"tunnel_ready\",\"listen_sock\":%d,\"timestamp\":%ld}\n", listen_sock, time(NULL));
fflush(stdout);
// Wait for 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) {
printf("{\"type\":\"script_error\",\"message\":\"Failed to accept connection\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
close(listen_sock);
return 1;
}
close(listen_sock);
// Send connection accepted message
printf("{\"type\":\"connection_established\",\"socket\":%d,\"timestamp\":%ld}\n", accepted_sock, time(NULL));
fflush(stdout);
// Send tunnel status information
printf("{\"type\":\"tunnel_status\",\"status\":\"active\",\"local_socket\":%d,\"request_id\":\"%s\",\"timestamp\":%ld}\n",
accepted_sock, active_tunnel->request_id, time(NULL));
fflush(stdout);
// Send transport information if available
if (active_tunnel) {
printf("{\"type\":\"transport_info\",\"data_channel\":\"%s\",\"control_channel\":\"%s\",\"timestamp\":%ld}\n",
config->tunnel ? config->tunnel : "any",
config->tunnel_control ? config->tunnel_control : "any",
time(NULL));
fflush(stdout);
}
// Set up tunnel for accepted connection
pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = accepted_sock;
// Send any buffered data info
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
printf("{\"type\":\"buffer_available\",\"bytes\":%zu,\"timestamp\":%ld}\n", active_tunnel->incoming_buffer->used, time(NULL));
fflush(stdout);
}
pthread_mutex_unlock(&tunnel_mutex);
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (thread_args) {
thread_args->ssl = active_tunnel->ssl;
thread_args->tunnel = active_tunnel;
thread_args->debug = config->debug;
pthread_t thread;
pthread_create(&thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(thread);
}
// Script mode main loop - handle WebSocket messages and script commands
printf("{\"type\":\"script_ready\",\"message\":\"Script mode active\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
// Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0;
// Buffer for processing messages
char buffer[BUFFER_SIZE];
int last_status_time = time(NULL);
while (1) {
// Check tunnel status
pthread_mutex_lock(&tunnel_mutex);
int tunnel_active = active_tunnel && active_tunnel->active;
int tunnel_broken = active_tunnel && active_tunnel->broken;
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
int ssl_fd = current_ssl ? SSL_get_fd(current_ssl) : -1;
pthread_mutex_unlock(&tunnel_mutex);
if (!tunnel_active) {
if (tunnel_broken) {
printf("{\"type\":\"tunnel_broken\",\"timestamp\":%ld}\n", time(NULL));
} else {
printf("{\"type\":\"tunnel_closed\",\"timestamp\":%ld}\n", time(NULL));
}
fflush(stdout);
break;
}
// Send periodic status updates every 30 seconds
int current_time = time(NULL);
if (current_time - last_status_time >= 30) {
printf("{\"type\":\"status\",\"message\":\"Tunnel active\",\"uptime\":%d,\"timestamp\":%d}\n",
current_time - last_status_time, (int)current_time);
fflush(stdout);
last_status_time = current_time;
}
// Check for stdin input and WebSocket data
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
if (ssl_fd >= 0) {
FD_SET(ssl_fd, &readfds);
}
tv.tv_sec = 0;
tv.tv_usec = 100000; // 100ms timeout
int max_fd = (ssl_fd > STDIN_FILENO) ? ssl_fd : STDIN_FILENO;
int retval = select(max_fd + 1, &readfds, NULL, NULL, &tv);
if (retval > 0) {
// Handle stdin input (script commands)
if (FD_ISSET(STDIN_FILENO, &readfds)) {
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
// EOF or error on stdin
printf("{\"type\":\"script_input_closed\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
// Remove trailing newline
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
}
// Process script command
printf("{\"type\":\"script_command\",\"command\":\"%s\",\"timestamp\":%ld}\n", buffer, time(NULL));
fflush(stdout);
// Handle basic script commands
if (strcmp(buffer, "status") == 0) {
printf("{\"type\":\"tunnel_status\",\"active\":%s,\"timestamp\":%ld}\n",
tunnel_active ? "true" : "false", time(NULL));
fflush(stdout);
} else if (strcmp(buffer, "close") == 0 || strcmp(buffer, "quit") == 0 || strcmp(buffer, "exit") == 0) {
// Send tunnel_close message before exiting
if (current_ssl && active_tunnel) {
printf("{\"type\":\"sending_tunnel_close\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
}
printf("{\"type\":\"script_ending\",\"reason\":\"user_request\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
}
// Handle WebSocket data (messages from server)
if (ssl_fd >= 0 && FD_ISSET(ssl_fd, &readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Use SSL to read WebSocket data
int bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used,
sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read > 0) {
frame_buffer_used += bytes_read;
if (config->debug) {
printf("[DEBUG - Script] Accumulated %d bytes from WebSocket\n", frame_buffer_used);
fflush(stdout);
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer for processing
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
// Determine channel and forward message
const char *channel = "control";
if (strstr(buffer, "\"type\":\"tunnel_data\"") || strstr(buffer, "tunnel_data") ||
strstr(buffer, "\"type\":\"tunnel_response\"") || strstr(buffer, "tunnel_response")) {
channel = "data";
}
// In script mode, only print control channel messages (not data channel)
if (strcmp(channel, "control") == 0) {
printf("{\"type\":\"message_received\",\"channel\":\"%s\",\"message\":\"%s\",\"timestamp\":%ld}\n",
channel, buffer, time(NULL));
fflush(stdout);
}
// Handle all message types
if (strstr(buffer, "tunnel_request")) {
printf("{\"type\":\"tunnel_request_received\",\"message\":\"Server requested tunnel\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
} else if (strstr(buffer, "tunnel_ack")) {
printf("{\"type\":\"tunnel_ack_received\",\"message\":\"Tunnel acknowledged by server\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
} else if (strstr(buffer, "tunnel_data")) {
// In script mode, suppress data channel message notifications
// Extract request_id and data for processing (but don't print)
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
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_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_response")) {
// In script mode, suppress data channel message notifications
// Extract request_id and data for processing (but don't print)
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
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_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_close")) {
printf("{\"type\":\"tunnel_close_received\",\"message\":\"Server closed tunnel\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
// Extract request_id and handle tunnel close
char *id_start = strstr(buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
handle_tunnel_close(current_ssl, id_start, config->debug);
}
}
}
}
} else if (strstr(buffer, "tunnel_error")) {
printf("{\"type\":\"tunnel_error_received\",\"message\":\"Tunnel error from server\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
} else {
printf("{\"type\":\"unknown_message_received\",\"message\":\"%s\",\"timestamp\":%ld}\n", buffer, time(NULL));
fflush(stdout);
}
}
} else if (frame_type == 0x88) { // Close frame
printf("{\"type\":\"websocket_close\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
} else if (frame_type == 0x89) { // Ping frame
printf("{\"type\":\"ping_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
// Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) {
printf("{\"type\":\"pong_send_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
} else if (frame_type == 0x8A) { // Pong frame
printf("{\"type\":\"pong_received\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
}
} else if (bytes_read == 0) {
// Connection closed
printf("{\"type\":\"websocket_connection_closed\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
} else {
// Error
printf("{\"type\":\"websocket_error\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
break;
}
}
}
}
// Small delay to prevent busy looping
usleep(25000); // 25ms
}
// Cleanup
printf("{\"type\":\"script_ended\",\"timestamp\":%ld}\n", time(NULL));
fflush(stdout);
return 0;
}
// Thread arguments structure for concurrent tunnel handling
typedef struct {
int accepted_sock;
wsssh_config_t *config;
const char *client_id;
const char *wssshd_host;
int wssshd_port;
const char *tunnel_host;
} tunnel_thread_args_t;
// Function to run tunnel in a separate thread
void *run_tunnel_thread(void *arg) {
tunnel_thread_args_t *args = (tunnel_thread_args_t *)arg;
// Establish the tunnel for this connection
int tunnel_sock = setup_tunnel(args->wssshd_host, args->wssshd_port, args->client_id, 0, args->config->debug, 0, args->tunnel_host);
if (tunnel_sock < 0) {
fprintf(stderr, "Failed to establish tunnel for connection\n");
close(args->accepted_sock);
free(args);
return NULL;
}
// Close the dummy listening socket created by setup_tunnel
close(tunnel_sock);
// Print tunnel information (unless silent mode)
if (args->config->mode != MODE_SILENT) {
printf("\n");
printf("========================================\n");
printf(" WEBSSH TUNNEL READY\n");
printf("========================================\n");
printf("Tunnel established successfully!\n");
printf("Local port: %d\n", atoi(args->config->local_port));
printf("Target: %s@%s\n", args->client_id, args->wssshd_host);
printf("\n");
printf("Connect manually using one of these commands:\n");
printf("\n");
printf(" Telnet:\n");
printf(" telnet localhost %d\n", atoi(args->config->local_port));
printf("\n");
printf(" Netcat:\n");
printf(" nc localhost %d\n", atoi(args->config->local_port));
printf("\n");
printf(" SSH (if connecting to SSH server):\n");
printf(" ssh -p %d user@localhost\n", atoi(args->config->local_port));
printf("\n");
printf(" SCP (if connecting to SSH server):\n");
printf(" scp -P %d user@localhost:/remote/path ./local/path\n", atoi(args->config->local_port));
printf("\n");
printf(" Any TCP client:\n");
printf(" Connect to localhost:%d\n", atoi(args->config->local_port));
printf("\n");
printf("Press Ctrl+C to close the tunnel and exit.\n");
printf("========================================\n");
printf("\n");
}
// Set the accepted socket with mutex protection
pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = args->accepted_sock;
// Send any buffered data to the client immediately
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending %zu bytes of buffered server response to client\n", active_tunnel->incoming_buffer->used);
fflush(stdout);
}
ssize_t sent = send(args->accepted_sock, active_tunnel->incoming_buffer->buffer, active_tunnel->incoming_buffer->used, 0);
if (sent > 0) {
frame_buffer_consume(active_tunnel->incoming_buffer, sent);
if (args->config->debug) {
printf("[DEBUG] Sent %zd bytes of buffered server response to client\n", sent);
fflush(stdout);
}
}
}
pthread_mutex_unlock(&tunnel_mutex);
if (args->config->debug) {
printf("[DEBUG - Tunnel] Local connection accepted! Starting data forwarding...\n");
fflush(stdout);
}
// Get initial SSL connection for thread
pthread_mutex_lock(&tunnel_mutex);
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
pthread_mutex_unlock(&tunnel_mutex);
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (!thread_args) {
perror("Memory allocation failed for thread args");
close(active_tunnel->local_sock);
free(active_tunnel);
active_tunnel = NULL;
free(args);
return NULL;
}
thread_args->ssl = current_ssl;
thread_args->tunnel = active_tunnel;
thread_args->debug = args->config->debug;
pthread_t forwarding_thread;
pthread_create(&forwarding_thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(forwarding_thread);
// Main tunnel loop - handle WebSocket messages
char buffer[BUFFER_SIZE];
int bytes_read;
fd_set tunnel_readfds;
struct timeval tunnel_tv;
// Frame accumulation buffer for handling partial WebSocket frames
char frame_buffer[BUFFER_SIZE * 4];
int frame_buffer_used = 0;
while (1) {
// Get SSL fd with mutex protection
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
if (active_tunnel && active_tunnel->broken) {
pthread_mutex_unlock(&tunnel_mutex);
goto thread_cleanup;
} else {
// normal closure
pthread_mutex_unlock(&tunnel_mutex);
if (args->config->debug) {
printf("[DEBUG - Tunnel] Tunnel is no longer active, exiting main loop\n");
fflush(stdout);
}
break;
}
}
int ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
// Check if local socket is still valid
if (active_tunnel->local_sock < 0) {
if (args->config->debug) {
printf("[DEBUG - Tunnel] Local socket is invalid, tunnel broken\n");
fflush(stdout);
}
active_tunnel->broken = 1;
// Send tunnel_close notification
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to invalid local socket...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto thread_cleanup;
}
// Check if the local socket connection is broken
char test_buf[1];
int result = recv(active_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE || errno == EBADF))) {
if (args->config->debug) {
printf("[DEBUG - Tunnel] Local socket connection is broken (errno=%d), sending tunnel_close\n", errno);
fflush(stdout);
}
active_tunnel->broken = 1;
// Send tunnel_close notification
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto thread_cleanup;
}
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on SSL socket with timeout
FD_ZERO(&tunnel_readfds);
FD_SET(ssl_fd, &tunnel_readfds);
tunnel_tv.tv_sec = 0;
tunnel_tv.tv_usec = 50000; // 50ms timeout
int retval = select(ssl_fd + 1, &tunnel_readfds, NULL, NULL, &tunnel_tv);
if (retval == -1) {
if (args->config->debug) {
perror("[DEBUG - WebSockets] select on SSL fd failed");
fflush(stdout);
}
// Send tunnel_close notification
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to select failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
goto thread_cleanup;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
if (args->config->debug) {
printf("[DEBUG - Tunnel] Tunnel became inactive during timeout, exiting\n");
fflush(stdout);
}
goto thread_cleanup;
}
pthread_mutex_unlock(&tunnel_mutex);
continue;
}
// Read more data if we don't have a complete frame
if (FD_ISSET(ssl_fd, &tunnel_readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Validate SSL connection state
if (SSL_get_shutdown(current_ssl) & SSL_RECEIVED_SHUTDOWN) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] SSL connection has received shutdown\n");
fflush(stdout);
}
cleanup_tunnel(args->config->debug);
break;
}
// Set up timeout for SSL read
fd_set readfds_timeout;
struct timeval tv_timeout;
int sock_fd = SSL_get_fd(current_ssl);
FD_ZERO(&readfds_timeout);
FD_SET(sock_fd, &readfds_timeout);
tv_timeout.tv_sec = 5;
tv_timeout.tv_usec = 0;
int select_result = select(sock_fd + 1, &readfds_timeout, NULL, NULL, &tv_timeout);
if (select_result == -1) {
if (args->config->debug) {
perror("[DEBUG - WebSockets] select failed");
fflush(stdout);
}
cleanup_tunnel(args->config->debug);
break;
} else if (select_result == 0) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] SSL read timeout\n");
fflush(stdout);
}
continue;
}
bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read <= 0) {
if (bytes_read < 0) {
int ssl_error = SSL_get_error(current_ssl, bytes_read);
if (args->config->debug) {
printf("[DEBUG - WebSockets] SSL read error: %d\n", ssl_error);
fflush(stdout);
}
// Handle transient SSL errors
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] Transient SSL error, retrying...\n");
fflush(stdout);
}
usleep(10000);
continue;
}
// Print SSL error details
char error_buf[256];
ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf));
if (args->config->debug) {
printf("[DEBUG - WebSockets] SSL error details: %s\n", error_buf);
fflush(stdout);
}
fprintf(stderr, "SSL read error (%d): %s\n", ssl_error, error_buf);
} else {
if (args->config->debug) {
printf("[DEBUG - WebSockets] Connection closed by server (EOF)\n");
fflush(stdout);
}
}
if (args->config->debug) {
printf("[DEBUG - WebSockets] WebSocket connection lost, attempting reconnection...\n");
fflush(stdout);
}
// Attempt reconnection
int reconnect_attempts = 0;
int max_reconnect_attempts = 3;
int reconnected = 0;
while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout);
}
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
if (reconnect_websocket(active_tunnel, args->wssshd_host, args->wssshd_port, args->client_id, active_tunnel->request_id, args->config->debug) == 0) {
reconnected = 1;
if (args->config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection successful, continuing tunnel\n");
fflush(stdout);
}
// Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
}
pthread_mutex_unlock(&tunnel_mutex);
if (!reconnected) {
reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection failed, waiting 1 second...\n");
fflush(stdout);
}
sleep(1);
}
}
}
if (!reconnected) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n");
fflush(stdout);
}
// Send tunnel_close notification
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to connection failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
goto thread_cleanup;
}
// Skip processing this iteration since we just reconnected
continue;
}
frame_buffer_used += bytes_read;
if (args->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]);
fflush(stdout);
}
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x88) { // Close frame
if (args->config->debug) {
printf("[DEBUG - WebSockets] Received close frame from server\n");
fflush(stdout);
}
// Send tunnel_close notification
if (args->config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to server close frame...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, args->config->debug);
goto thread_cleanup;
} else if (frame_type == 0x89) { // Ping frame
if (args->config->debug) {
printf("[DEBUG - WebSockets] Received ping frame, sending pong\n");
fflush(stdout);
}
// Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) {
if (args->config->debug) {
printf("[DEBUG - WebSockets] Failed to send pong frame\n");
fflush(stdout);
}
}
} else if (frame_type == 0x8A) { // Pong frame
if (args->config->debug) {
printf("[DEBUG - WebSockets] Received pong frame\n");
fflush(stdout);
}
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
} else {
fprintf(stderr, "Payload too large for processing buffer\n");
frame_buffer_used = 0;
continue;
}
// Check if this is a data message to suppress verbose logging
int is_data_message = (strstr(buffer, "\"type\":\"tunnel_data\"") != NULL ||
strstr(buffer, "\"type\":\"tunnel_response\"") != NULL);
if (args->config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// Handle message
if (args->config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
fflush(stdout);
}
// Handle tunnel messages
if (strstr(buffer, "tunnel_data") || strstr(buffer, "tunnel_response")) {
if (args->config->debug) {
// Suppress tunnel_data debug messages in debug mode
if (!strstr(buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] Received tunnel_response message\n");
fflush(stdout);
}
}
// Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
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_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, args->config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_close")) {
if (args->config->debug) {
printf("[DEBUG - Tunnel] Received tunnel_close message\n");
fflush(stdout);
}
char *id_start = strstr(buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
handle_tunnel_close(current_ssl, id_start, args->config->debug);
}
}
}
}
} else {
if (args->config->debug) {
printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer);
fflush(stdout);
}
}
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
} else {
// Frame not complete yet, continue reading
continue;
}
}
}
thread_cleanup:
// Cleanup section
if (args->config->debug) {
printf("[DEBUG - Tunnel] Performing cleanup and exiting\n");
fflush(stdout);
}
// Cleanup
if (active_tunnel) {
if (active_tunnel->local_sock >= 0) {
close(active_tunnel->local_sock);
}
if (active_tunnel->ssl) {
SSL_free(active_tunnel->ssl);
}
free(active_tunnel);
active_tunnel = NULL;
}
free(args);
return NULL;
}
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) {
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);
}
// Daemon mode main loop - accept connections and establish tunnels
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, establishing tunnel...\n", local_port);
fflush(stdout);
}
// Now establish the tunnel for this connection
// In daemon mode, use port 0 to let setup_tunnel find an available port for WebSocket setup,
// but we'll use our already accepted socket for the local connection
int tunnel_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, 0, config->debug, 0, config->tunnel_host);
if (tunnel_sock < 0) {
fprintf(stderr, "Failed to establish tunnel for connection\n");
close(accepted_sock);
continue; // Don't exit, keep listening for new connections
}
// Close the dummy listening socket created by setup_tunnel
close(tunnel_sock);
// Print tunnel information (unless silent mode)
if (config->mode != MODE_SILENT) {
printf("\n");
printf("========================================\n");
printf(" WEBSSH TUNNEL READY\n");
printf("========================================\n");
printf("Tunnel established successfully!\n");
printf("Local port: %d\n", local_port);
printf("Target: %s@%s\n", client_id, wssshd_host);
printf("\n");
printf("Connect manually using one of these commands:\n");
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
// Set the accepted socket with mutex protection
pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = accepted_sock;
// Send any buffered data to the client immediately
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
if (config->debug) {
printf("[DEBUG - Tunnel] Sending %zu bytes of buffered server response to client\n", active_tunnel->incoming_buffer->used);
fflush(stdout);
}
ssize_t sent = send(accepted_sock, active_tunnel->incoming_buffer->buffer, active_tunnel->incoming_buffer->used, 0);
if (sent > 0) {
frame_buffer_consume(active_tunnel->incoming_buffer, sent);
if (config->debug) {
printf("[DEBUG] Sent %zd bytes of buffered server response to client\n", sent);
fflush(stdout);
}
}
}
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Local connection accepted! Starting data forwarding...\n");
fflush(stdout);
}
// Get initial SSL connection for thread
pthread_mutex_lock(&tunnel_mutex);
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
pthread_mutex_unlock(&tunnel_mutex);
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (!thread_args) {
perror("Memory allocation failed for thread args");
close(active_tunnel->local_sock);
free(active_tunnel);
active_tunnel = NULL;
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
thread_args->ssl = current_ssl;
thread_args->tunnel = active_tunnel;
thread_args->debug = config->debug;
pthread_t thread;
pthread_create(&thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(thread);
// Main tunnel loop - handle WebSocket messages
char buffer[BUFFER_SIZE];
int bytes_read;
fd_set tunnel_readfds;
struct timeval tunnel_tv;
int tunnel_broken = 0;
// Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0;
while (1) {
// Get SSL fd with mutex protection
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
if (active_tunnel && active_tunnel->broken) {
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
} else {
// normal closure
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel is no longer active, exiting main loop\n");
fflush(stdout);
}
break;
}
}
int ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
// Check if local socket is still valid
if (active_tunnel->local_sock < 0) {
if (config->debug) {
printf("[DEBUG - Tunnel] Local socket is invalid, tunnel broken\n");
fflush(stdout);
}
active_tunnel->broken = 1;
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to invalid local socket...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
}
// Check if the local socket connection is broken
char test_buf[1];
int result = recv(active_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE || errno == EBADF))) {
if (config->debug) {
printf("[DEBUG - Tunnel] Local socket connection is broken (errno=%d), sending tunnel_close\n", errno);
fflush(stdout);
}
active_tunnel->broken = 1;
// Send tunnel_close notification
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on SSL socket with timeout
FD_ZERO(&tunnel_readfds);
FD_SET(ssl_fd, &tunnel_readfds);
tunnel_tv.tv_sec = 0;
tunnel_tv.tv_usec = 50000; // 50ms timeout
int retval = select(ssl_fd + 1, &tunnel_readfds, NULL, NULL, &tunnel_tv);
if (retval == -1) {
if (config->debug) {
perror("[DEBUG - WebSockets] select on SSL fd failed");
fflush(stdout);
}
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to select failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel became inactive during timeout, exiting\n");
fflush(stdout);
}
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
continue;
}
// Read more data if we don't have a complete frame
if (FD_ISSET(ssl_fd, &tunnel_readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Validate SSL connection state
if (SSL_get_shutdown(current_ssl) & SSL_RECEIVED_SHUTDOWN) {
if (config->debug) {
printf("[DEBUG - WebSockets] SSL connection has received shutdown\n");
fflush(stdout);
}
cleanup_tunnel(config->debug);
break;
}
// Set up timeout for SSL read
fd_set readfds_timeout;
struct timeval tv_timeout;
int sock_fd = SSL_get_fd(current_ssl);
FD_ZERO(&readfds_timeout);
FD_SET(sock_fd, &readfds_timeout);
tv_timeout.tv_sec = 5;
tv_timeout.tv_usec = 0;
int select_result = select(sock_fd + 1, &readfds_timeout, NULL, NULL, &tv_timeout);
if (select_result == -1) {
if (config->debug) {
perror("[DEBUG - WebSockets] select failed");
fflush(stdout);
}
cleanup_tunnel(config->debug);
break;
} else if (select_result == 0) {
if (config->debug) {
printf("[DEBUG - WebSockets] SSL read timeout\n");
fflush(stdout);
}
continue;
}
bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read <= 0) {
if (bytes_read < 0) {
int ssl_error = SSL_get_error(current_ssl, bytes_read);
if (config->debug) {
printf("[DEBUG - WebSockets] SSL read error: %d\n", ssl_error);
fflush(stdout);
}
// Handle transient SSL errors
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
if (config->debug) {
printf("[DEBUG - WebSockets] Transient SSL error, retrying...\n");
fflush(stdout);
}
usleep(10000);
continue;
}
// Print SSL error details
char error_buf[256];
ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf));
if (config->debug) {
printf("[DEBUG - WebSockets] SSL error details: %s\n", error_buf);
fflush(stdout);
}
fprintf(stderr, "SSL read error (%d): %s\n", ssl_error, error_buf);
} else {
if (config->debug) {
printf("[DEBUG - WebSockets] Connection closed by server (EOF)\n");
fflush(stdout);
}
}
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket connection lost, attempting reconnection...\n");
fflush(stdout);
}
// Attempt reconnection
int reconnect_attempts = 0;
int max_reconnect_attempts = 3;
int reconnected = 0;
while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout);
}
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
if (reconnect_websocket(active_tunnel, wssshd_host, wssshd_port, client_id, active_tunnel->request_id, config->debug) == 0) {
reconnected = 1;
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection successful, continuing tunnel\n");
fflush(stdout);
}
// Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
}
pthread_mutex_unlock(&tunnel_mutex);
if (!reconnected) {
reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) {
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection failed, waiting 1 second...\n");
fflush(stdout);
}
sleep(1);
}
}
}
if (!reconnected) {
if (config->debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n");
fflush(stdout);
}
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to connection failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
}
// Skip processing this iteration since we just reconnected
continue;
}
frame_buffer_used += bytes_read;
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]);
fflush(stdout);
}
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x88) { // Close frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received close frame from server\n");
fflush(stdout);
}
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to server close frame...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
} else if (frame_type == 0x89) { // Ping frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received ping frame, sending pong\n");
fflush(stdout);
}
// Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) {
if (config->debug) {
printf("[DEBUG - WebSockets] Failed to send pong frame\n");
fflush(stdout);
}
}
} else if (frame_type == 0x8A) { // Pong frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received pong frame\n");
fflush(stdout);
}
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
} else {
fprintf(stderr, "Payload too large for processing buffer\n");
frame_buffer_used = 0;
continue;
}
// Check if this is a data message to suppress verbose logging
int is_data_message = (strstr(buffer, "\"type\": \"tunnel_data\"") != NULL ||
strstr(buffer, "\"type\": \"tunnel_response\"") != NULL);
if (config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// Handle message
if (config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
fflush(stdout);
}
// Handle tunnel messages
if (strstr(buffer, "tunnel_data") || strstr(buffer, "tunnel_response")) {
if (config->debug) {
// Suppress tunnel_data debug messages in debug mode
if (!strstr(buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] Received tunnel_response message\n");
fflush(stdout);
}
}
// Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
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_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_close")) {
if (config->debug) {
printf("[DEBUG - Tunnel] Received tunnel_close message\n");
fflush(stdout);
}
char *id_start = strstr(buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
handle_tunnel_close(current_ssl, id_start, config->debug);
}
}
}
}
} else {
if (config->debug) {
printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer);
fflush(stdout);
}
}
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
} else {
// Frame not complete yet, continue reading
continue;
}
}
}
cleanup_and_exit:
// Cleanup section
if (config->debug) {
printf("[DEBUG - Tunnel] Performing cleanup and exiting\n");
fflush(stdout);
}
// Cleanup
if (active_tunnel) {
if (active_tunnel->local_sock >= 0) {
close(active_tunnel->local_sock);
}
if (active_tunnel->ssl) {
SSL_free(active_tunnel->ssl);
}
free(active_tunnel);
active_tunnel = NULL;
}
free(config->local_port);
free(config->tunnel);
free(config->tunnel_control);
free(config->service);
// Set to NULL to prevent double-free in main()
config->local_port = 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);
if (config->debug) {
printf("[DEBUG - Tunnel] Cleanup complete, exiting with code %d\n", tunnel_broken ? 1 : 0);
fflush(stdout);
}
// In daemon mode, continue to accept new connections
// Continue the while(1) loop for next connection
}
// This should never be reached, but just in case
return 0;
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Read config from wsssht.conf // Read config from wsssht.conf
......
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