feat: Add wsssht - WebSocket SSH Tunnel tool

- Create wsssht.c: Stripped-down tunnel setup tool (like wsssh --dev-tunnel)
- Add separate wssht.conf configuration file support
- Implement read_config_value_from_file() for custom config files
- Create wssht.conf.example with tunnel configuration options
- Update wsssht.1 man page with separate config file documentation
- Add wsssht to build system (configure.sh, Makefile)
- Test successful compilation and functionality

wsssht provides manual tunnel setup without auto-executing SSH/SCP,
displaying connection information for telnet, nc, or any TCP client.
parent cc27f590
# WebSocket SSH Tunnel (wsssht) Configuration Example
#
# This is an example configuration file for wsssht.
# Copy this file to ~/.config/wsssh/wssht.conf
# and modify the settings as needed.
#
# Configuration options:
# port: Default WebSocket server port
# domain: Default domain suffix for hostname parsing
# tunnel: Default transport types for data channel (comma-separated or 'any')
# tunnel-control: Default transport types for control channel (comma-separated or 'any')
# service: Default service type (default: ssh)
# interval: Connection retry interval in seconds (default: 30)
[default]
port=9898
domain=example.com
tunnel=websocket
tunnel-control=websocket
service=ssh
interval=30
\ No newline at end of file
.TH WSSSHT 1 "September 2024" "WebSocket SSH" "User Commands"
.SH NAME
wsssht \- WebSocket SSH Tunnel Setup Tool
.SH SYNOPSIS
.B wsssht
[\fB\-\-local\-port\fR \fIPORT\fR]
[\fB\-\-interval\fR \fISEC\fR]
[\fB\-\-debug\fR]
[\fB\-\-tunnel\fR \fITYPES\fR]
[\fB\-\-tunnel\-control\fR \fITYPES\fR]
[\fB\-\-service\fR \fISERVICE\fR]
[\fB\-\-help\fR]
[\fB\-p\fR \fIPORT\fR]
[\fB\-\-port\fR \fIPORT\fR]
\fIuser\fR@\fIclient\fR.\fIdomain\fR
.SH DESCRIPTION
.B wsssht
is a WebSocket SSH tunnel setup tool that establishes secure tunnels through WebSocket connections without automatically executing SSH/SCP commands. It provides connection information for manual use with any TCP client.
.PP
Unlike
.BR wsssh (1)
and
.BR wsscp (1),
.B wsssht
does not fork and execute external commands. Instead, it sets up the tunnel and displays connection instructions for manual use.
.SH OPTIONS
.TP
.BR \-\-local\-port " \fIPORT\fR"
Specify the local port for the tunnel (default: auto\-assigned)
.TP
.BR \-\-interval " \fISEC\fR"
Connection retry interval in seconds (default: 30)
.TP
.BR \-\-debug
Enable debug output
.TP
.BR \-\-tunnel " \fITYPES\fR"
Transport types for data channel (comma\-separated or 'any', default: any)
.TP
.BR \-\-tunnel\-control " \fITYPES\fR"
Transport types for control channel (comma\-separated or 'any', default: any)
.TP
.BR \-\-service " \fISERVICE\fR"
Service type (default: ssh)
.TP
.BR \-\-help
Display help message and exit
.TP
.BR \-p ", " \-\-port " \fIPORT\fR"
WebSocket SSH daemon server port (default: 9898)
.SH EXAMPLES
.TP
Basic tunnel setup:
.B wsssht user@myclient.example.com
.TP
Specify local port:
.B wsssht \-\-local\-port 2222 user@myclient.example.com
.TP
Use specific transport:
.B wsssht \-\-tunnel websocket user@myclient.example.com
.TP
Debug mode:
.B wsssht \-\-debug user@myclient.example.com
.SH MANUAL CONNECTION
Once the tunnel is established,
.B wsssht
displays connection information:
.PP
.RS
.nf
========================================
WEBSSH TUNNEL READY
========================================
Tunnel established successfully!
Local port: 49234
Target: user@myclient.example.com
Connect manually using one of these commands:
Telnet:
telnet localhost 49234
Netcat:
nc localhost 49234
SSH (if connecting to SSH server):
ssh -p 49234 user@localhost
SCP (if connecting to SSH server):
scp -P 49234 user@localhost:/remote/path ./local/path
Any TCP client:
Connect to localhost:49234
Press Ctrl+C to close the tunnel and exit.
========================================
.fi
.RE
.SH CONFIGURATION
.B wsssht
supports a separate configuration file at
.I ~/.config/wsssh/wssht.conf
with the following options:
.PP
.RS
.nf
[default]
port=9898
domain=example.com
tunnel=websocket
tunnel-control=websocket
service=ssh
interval=30
.fi
.RE
.PP
Note: Unlike
.BR wsssh (1)
and
.BR wsscp (1),
.B wsssht
uses its own configuration file
.I wssht.conf
instead of sharing
.I wsssh.conf.
.SH SEE ALSO
.BR wsssh (1),
.BR wsscp (1),
.BR wssshc (1),
.BR wssshd (1)
.SH AUTHOR
Written by Stefy Lanza <stefy@nexlab.net> and SexHack.me
.SH COPYRIGHT
Copyright \(co 2024 Stefy Lanza and SexHack.me
.PP
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.
.SH BUGS
Report bugs to https://git.nexlab.net/nexlab/wsssh
\ No newline at end of file
...@@ -91,6 +91,50 @@ char *read_config_value(const char *key) { ...@@ -91,6 +91,50 @@ char *read_config_value(const char *key) {
return NULL; return NULL;
} }
char *read_config_value_from_file(const char *key, const char *config_file) {
char *home = getenv("HOME");
if (!home) return NULL;
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/.config/wsssh/%s.conf", home, config_file);
FILE *f = fopen(path, "r");
if (!f) return NULL;
char line[256];
char section[64] = "";
while (fgets(line, sizeof(line), f)) {
// Check for section headers
if (line[0] == '[') {
sscanf(line, "[%63[^]]", section);
continue;
}
// Skip comments and empty lines
if (line[0] == '#' || line[0] == ';' || line[0] == '\n') continue;
char *equals = strchr(line, '=');
if (equals) {
*equals = '\0';
char *config_key = line;
char *value = equals + 1;
// Trim whitespace
while (*config_key == ' ' || *config_key == '\t') config_key++;
char *end = config_key + strlen(config_key) - 1;
while (end > config_key && (*end == ' ' || *end == '\t')) *end-- = '\0';
while (*value == ' ' || *value == '\t') value++;
end = value + strlen(value) - 1;
while (end > value && (*end == ' ' || *end == '\t' || *end == '\n')) *end-- = '\0';
// Check if key matches (case-insensitive for section names)
if (strcmp(config_key, key) == 0) {
fclose(f);
return strdup(value);
}
}
}
fclose(f);
return NULL;
}
void print_trans_flag(void) { void print_trans_flag(void) {
// Transgender pride flag colors using ANSI escape codes // Transgender pride flag colors using ANSI escape codes
const char *colors[] = { const char *colors[] = {
......
...@@ -88,6 +88,7 @@ typedef struct { ...@@ -88,6 +88,7 @@ typedef struct {
// Function declarations // Function declarations
char *read_config_value(const char *key); char *read_config_value(const char *key);
char *read_config_value_from_file(const char *key, const char *config_file);
char *expand_transport_list(const char *transport_spec, int for_relay); char *expand_transport_list(const char *transport_spec, int for_relay);
char *select_best_transport(const char *transport_list); char *select_best_transport(const char *transport_list);
int get_transport_weight(const char *transport); int get_transport_weight(const char *transport);
......
/*
* 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/>.
*/
#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"
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options] user@client.domain\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, " --local-port PORT Local tunnel port (default: auto)\n");
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n");
fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --tunnel TYPES Transport types for data channel (comma-separated or 'any', 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, " --help Show this help\n");
fprintf(stderr, " -p PORT wssshd server port (default: 9898)\n");
fprintf(stderr, " --port PORT Same as -p\n");
fprintf(stderr, "\nExamples:\n");
fprintf(stderr, " %s user@myclient.example.com -p 9898\n", program_name);
fprintf(stderr, " %s --local-port 2222 user@myclient.example.com\n", program_name);
fprintf(stderr, " %s --tunnel websocket --debug user@myclient.example.com\n", program_name);
fprintf(stderr, "\nDonations:\n");
fprintf(stderr, " BTC: bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\n");
fprintf(stderr, " ETH: 0xdA6dAb526515b5cb556d20269207D43fcc760E51\n");
}
int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_argc, char ***remaining_argv) {
// Manually parse arguments to separate wsssht options from target
int target_start = 1; // Skip argv[0]
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--local-port") == 0 && i + 1 < argc) {
config->local_port = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
config->wssshd_port = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--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], "--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]);
return 0;
} else {
// Non-option argument, start of target
target_start = i;
break;
}
}
// Return remaining arguments (target)
*remaining_argc = argc - target_start;
*remaining_argv = &argv[target_start];
return 1;
}
int parse_hostname(const char *hostname, char **client_id, char **wssshd_host, const char *config_domain) {
char *at_pos = strchr(hostname, '@');
if (!at_pos) {
fprintf(stderr, "Error: Invalid hostname format. Expected user@host\n");
return 0;
}
char *host_part = at_pos + 1;
char *host_copy = strdup(host_part);
if (!host_copy) {
return 0;
}
// Remove port if present
char *colon_pos = strchr(host_copy, ':');
if (colon_pos) {
*colon_pos = '\0';
}
// Split host by dots to extract client_id
char *dot_pos = strchr(host_copy, '.');
if (!dot_pos) {
// No domain, use config
if (!config_domain) {
fprintf(stderr, "Error: Invalid hostname format. Expected client.domain format or domain in config\n");
free(host_copy);
return 0;
}
*client_id = strdup(host_copy);
*wssshd_host = strdup(config_domain);
} else {
*dot_pos = '\0';
*client_id = strdup(host_copy);
*wssshd_host = strdup(dot_pos + 1);
}
free(host_copy);
return 1;
}
int parse_target_args(int argc, char *argv[], char **host, int debug) {
*host = NULL;
for (int i = 0; i < argc; i++) {
if (argv[i][0] != '-' && !*host) {
// First non-option argument should be the target host
*host = argv[i];
if (debug) {
printf("[DEBUG - Tunnel] Found target host: %s\n", *host);
fflush(stdout);
}
break;
}
}
if (!*host) {
fprintf(stderr, "Error: Could not determine target host\n");
return 0;
}
return 1;
}
int main(int argc, char *argv[]) {
// Read config from wssht.conf
char *config_domain = read_config_value_from_file("domain", "wssht");
char *config_tunnel = read_config_value_from_file("tunnel", "wssht");
char *config_tunnel_control = read_config_value_from_file("tunnel-control", "wssht");
char *config_service = read_config_value_from_file("service", "wssht");
char *config_interval = read_config_value_from_file("interval", "wssht");
wsssh_config_t config = {
.local_port = NULL,
.wssshd_port = 9898,
.debug = 0,
.interval = 30,
.dev_tunnel = 0,
.tunnel = config_tunnel,
.tunnel_control = config_tunnel_control,
.service = config_service
};
// Set defaults if not provided
if (!config.tunnel) {
config.tunnel = strdup("any");
}
if (!config.tunnel_control) {
config.tunnel_control = strdup("any");
}
if (!config.service) {
config.service = strdup("ssh");
}
if (config_interval) {
config.interval = atoi(config_interval);
free(config_interval);
}
// Easter egg: --support option (only when it's the only argument)
if (argc == 2 && strcmp(argv[1], "--support") == 0) {
print_trans_flag();
free(config_domain);
return 0;
}
// Handle --help and --free before parsing
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_usage(argv[0]);
free(config_domain);
return 0;
} else if (strcmp(argv[i], "--free") == 0) {
print_palestinian_flag();
free(config_domain);
return 0;
}
}
pthread_mutex_init(&tunnel_mutex, NULL);
pthread_mutex_init(&ssl_mutex, NULL);
// Parse wsssht arguments
int remaining_argc;
char **remaining_argv;
if (!parse_args(argc, argv, &config, &remaining_argc, &remaining_argv)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
}
// Need at least one target argument
if (remaining_argc == 0) {
fprintf(stderr, "Error: No target specified\n");
print_usage(argv[0]);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
// Parse target arguments to extract host
char *target_host = NULL;
if (!parse_target_args(remaining_argc, remaining_argv, &target_host, config.debug)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
}
// Parse the target host to extract client_id and wssshd_host
char *client_id = NULL;
char *wssshd_host = NULL;
int wssshd_port = config.wssshd_port;
if (!parse_hostname(target_host, &client_id, &wssshd_host, config_domain)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
}
if (config.debug) {
printf("[DEBUG - Tunnel] Target Host: %s\n", target_host);
printf("[DEBUG - Tunnel] Client ID: %s\n", client_id);
printf("[DEBUG - Tunnel] WSSSHD Host: %s\n", wssshd_host);
printf("[DEBUG - Tunnel] WSSSHD Port: %d\n", wssshd_port);
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");
free(client_id);
free(wssshd_host);
free(config.local_port);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
if (config.debug) {
printf("[DEBUG - Tunnel] Using local port: %d\n", local_port);
fflush(stdout);
}
// Parent process: setup tunnel
if (config.debug) {
printf("[DEBUG - Tunnel] Setting up tunnel...\n");
fflush(stdout);
}
// Attempt initial tunnel setup with retry logic using --interval
int listen_sock = -1;
int setup_attempts = 0;
int max_setup_attempts = 3;
while (setup_attempts < max_setup_attempts && listen_sock < 0) {
if (config.debug && setup_attempts > 0) {
printf("[DEBUG - Tunnel] Tunnel setup attempt %d/%d\n", setup_attempts + 1, max_setup_attempts);
fflush(stdout);
}
listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug, 0);
if (listen_sock < 0) {
setup_attempts++;
if (setup_attempts < max_setup_attempts) {
if (config.debug) {
printf("[DEBUG - Tunnel] Tunnel setup failed, waiting %d seconds...\n", config.interval);
fflush(stdout);
}
sleep(config.interval);
}
}
}
if (listen_sock < 0) {
fprintf(stderr, "Error: Failed to establish tunnel after %d attempts\n", max_setup_attempts);
free(client_id);
free(wssshd_host);
free(config.local_port);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
// Print tunnel information and connection instructions
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");
// Start forwarding threads to handle bidirectional communication
// Parent process: accept connection and start forwarding
if (config.debug) {
printf("[DEBUG - Tunnel] Waiting for connection on localhost:%d...\n", local_port);
fflush(stdout);
}
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) {
perror("Local accept failed");
close(listen_sock);
free(active_tunnel);
active_tunnel = NULL;
free(client_id);
free(wssshd_host);
free(config.local_port);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
close(listen_sock); // No longer needed
// 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;
free(client_id);
free(wssshd_host);
free(config.local_port);
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 readfds;
struct timeval 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) {
tunnel_broken = 1;
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);
}
tunnel_broken = 1;
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);
}
tunnel_broken = 1;
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(&readfds);
FD_SET(ssl_fd, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 50000; // 50ms timeout
int retval = select(ssl_fd + 1, &readfds, NULL, NULL, &tv);
if (retval == -1) {
if (config.debug) {
perror("[DEBUG - WebSockets] select on SSL fd failed");
fflush(stdout);
}
tunnel_broken = 1;
// 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);
}
tunnel_broken = 1;
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, &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);
}
tunnel_broken = 1;
// 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);
}
tunnel_broken = 1;
// 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
if (config.debug) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// 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;
}
// Handle message
if (config.debug) {
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) {
if (strstr(buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] Received tunnel_data message\n");
} else {
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(client_id);
free(wssshd_host);
free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
free(config_domain);
free(config_tunnel);
free(config_tunnel_control);
free(config_service);
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);
}
return tunnel_broken ? 1 : 0;
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment