Remove wsssh and wsscp from the project

- Remove wsssh.c and wsscp.c source files
- Remove wsssh.1 and wsscp.1 man pages
- Remove wsssh.conf.example and wsscp.conf.example config files
- Update configure.sh to exclude wsssh and wsscp from build
- Update debian/control to reflect remaining tools (wssshc + wsssht)
- Verified build works with only wssshc and wsssht binaries
- Project now focuses on wssshc (client) and wsssht (tunnel tool with pipe mode)
parent 76ac4c24
...@@ -58,12 +58,12 @@ LDFLAGS = $(shell pkg-config --libs openssl) ...@@ -58,12 +58,12 @@ LDFLAGS = $(shell pkg-config --libs openssl)
# Source files # Source files
LIB_SRCS = libwsssht/wssshlib.c libwsssht/websocket.c libwsssht/wssh_ssl.c libwsssht/tunnel.c libwsssht/utils.c libwsssht/modes.c libwsssht/threads.c LIB_SRCS = libwsssht/wssshlib.c libwsssht/websocket.c libwsssht/wssh_ssl.c libwsssht/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 wsssht.c
OBJS = $(SRCS:.c=.o) OBJS = $(SRCS:.c=.o)
TARGETS = wssshc wsssh wsscp wsssht TARGETS = wssshc wsssht
# Man pages # Man pages
MANPAGES = man/wssshc.1 man/wsssh.1 man/wsscp.1 man/wsssht.1 MANPAGES = man/wssshc.1 man/wsssht.1
# Default target # Default target
all: $(TARGETS) all: $(TARGETS)
...@@ -72,12 +72,6 @@ all: $(TARGETS) ...@@ -72,12 +72,6 @@ all: $(TARGETS)
wssshc: wssshc.o libwsssht/wssshlib.o libwsssht/websocket.o libwsssht/wssh_ssl.o libwsssht/tunnel.o wssshc: wssshc.o libwsssht/wssshlib.o libwsssht/websocket.o libwsssht/wssh_ssl.o libwsssht/tunnel.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsssh: wsssh.o libwsssht/wssshlib.o libwsssht/websocket.o libwsssht/wssh_ssl.o libwsssht/tunnel.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsscp: wsscp.o libwsssht/wssshlib.o libwsssht/websocket.o libwsssht/wssh_ssl.o libwsssht/tunnel.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsssht: wsssht.o $(LIB_OBJS) wsssht: wsssht.o $(LIB_OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
...@@ -108,20 +102,12 @@ install: all ...@@ -108,20 +102,12 @@ install: all
uninstall: uninstall:
# Remove from both possible locations # Remove from both possible locations
rm -f $(DESTDIR)/usr/local/bin/wssshc rm -f $(DESTDIR)/usr/local/bin/wssshc
rm -f $(DESTDIR)/usr/local/bin/wsssh
rm -f $(DESTDIR)/usr/local/bin/wsscp
rm -f $(DESTDIR)/usr/local/bin/wsssht rm -f $(DESTDIR)/usr/local/bin/wsssht
rm -f $(DESTDIR)/usr/local/share/man/man1/wssshc.1 rm -f $(DESTDIR)/usr/local/share/man/man1/wssshc.1
rm -f $(DESTDIR)/usr/local/share/man/man1/wsssh.1
rm -f $(DESTDIR)/usr/local/share/man/man1/wsscp.1
rm -f $(DESTDIR)/usr/local/share/man/man1/wsssht.1 rm -f $(DESTDIR)/usr/local/share/man/man1/wsssht.1
rm -f $(DESTDIR)/usr/bin/wssshc rm -f $(DESTDIR)/usr/bin/wssshc
rm -f $(DESTDIR)/usr/bin/wsssh
rm -f $(DESTDIR)/usr/bin/wsscp
rm -f $(DESTDIR)/usr/bin/wsssht rm -f $(DESTDIR)/usr/bin/wsssht
rm -f $(DESTDIR)/usr/share/man/man1/wssshc.1 rm -f $(DESTDIR)/usr/share/man/man1/wssshc.1
rm -f $(DESTDIR)/usr/share/man/man1/wsssh.1
rm -f $(DESTDIR)/usr/share/man/man1/wsscp.1
rm -f $(DESTDIR)/usr/share/man/man1/wsssht.1 rm -f $(DESTDIR)/usr/share/man/man1/wsssht.1
.PHONY: all clean install uninstall .PHONY: all clean install uninstall
......
...@@ -16,5 +16,4 @@ Description: WebSocket SSH Tools - C implementation ...@@ -16,5 +16,4 @@ Description: WebSocket SSH Tools - C implementation
route SSH/SCP traffic through registered client machines. route SSH/SCP traffic through registered client machines.
. .
This package contains the C implementation of the WebSocket SSH tools: This package contains the C implementation of the WebSocket SSH tools:
wssshc (client registration), wsssh (SSH wrapper), wsscp (SCP wrapper), wssshc (client registration) and wsssht (tunnel setup tool with pipe mode).
and wsssht (tunnel setup tool). \ No newline at end of file
\ No newline at end of file
.TH WSSCP 1 "September 2025" "wsssh-tools 1.4.0" "WebSocket SSH Tools"
.SH NAME
wsscp \- WebSocket SCP wrapper for secure file transfer
.SH SYNOPSIS
.B wsscp
[\fB\-\-local\-port\fR \fIPORT\fR]
[\fB\-\-tunnel\fR \fITYPES\fR]
[\fB\-\-tunnel\-control\fR \fITYPES\fR]
[\fB\-\-service\fR \fISERVICE\fR]
[\fB\-\-debug\fR]
[\fB\-\-dev\-tunnel\fR]
[\fB\-\-help\fR]
[\fISCP_OPTIONS\fR...]
\fISOURCE\fR...
\fIDESTINATION\fR
.SH DESCRIPTION
.B wsscp
is a wrapper around the standard SCP client that enables secure file transfer through WebSocket connections. It automatically establishes tunnels to registered clients and handles file transfers transparently.
.SH OPTIONS
.TP
.BR \-\-local\-port " \fIPORT\fR"
Local port for tunnel establishment (default: auto-assign)
.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: scp)
.TP
.B \-\-debug
Enable debug output for troubleshooting
.TP
.B \-\-dev\-tunnel
Development mode: setup tunnel but don't launch SCP
.TP
.B \-\-help
Display help message and exit
.SH HOSTNAME FORMAT
Hostnames in source/destination paths follow the format: [\fIuser\fR@]\fIclient_id\fR.\fIwssshd_host\fR[:\fIport\fR]
.TP
Examples:
- \fBuser@remote.example.com:/path/file\fR → client: \fBremote\fR, server: \fBexample.com:9898\fR
- \fBserver.datacenter.com:2222/file\fR → client: \fBserver\fR, server: \fBdatacenter.com:2222\fR
.SH EXAMPLES
.TP
Copy file to remote:
.B wsscp -P 9898 localfile user@myclient.example.com:/remote/path/
.TP
Copy file from remote:
.B wsscp -P 9898 user@myclient.example.com:/remote/file ./localfile
.TP
Copy directory recursively:
.B wsscp -P 9898 -r localdir user@myclient.example.com:/remote/
.TP
Copy with debug output:
.B wsscp --debug -P 2222 file.txt user@remote.example.com:~/downloads/
.SH SCP OPTIONS
All standard SCP options are supported. The port option (\fB-P\fR) specifies the wssshd server port, not the target SSH port.
.SH ENVIRONMENT
.TP
.B WSSCP_DEBUG
Set to 1 to enable debug output (alternative to --debug)
.SH SECURITY
File transfers are secured through SSL/TLS encrypted WebSocket tunnels. All data in transit is protected by the tunnel encryption.
.SH EXIT STATUS
.B wsscp
returns the exit status of the underlying SCP command.
.SH SEE ALSO
.BR scp (1),
.BR wssshd (1),
.BR wssshc (1),
.BR wsssh (1)
.SH AUTHOR
Written by Stefy Lanza <stefy@nexlab.net>
.SH COPYRIGHT
Copyright \(co 2024 Stefy Lanza and SexHack.me. License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
.SH BUGS
Report bugs to: https://git.nexlab.net/nexlab/wsssh/issues
\ No newline at end of file
.TH WSSH 1 "September 2025" "wsssh-tools 1.4.0" "WebSocket SSH Tools"
.SH NAME
wsssh \- WebSocket SSH wrapper for secure tunneling
.SH SYNOPSIS
.B wsssh
[\fB\-\-local\-port\fR \fIPORT\fR]
[\fB\-\-tunnel\fR \fITYPES\fR]
[\fB\-\-tunnel\-control\fR \fITYPES\fR]
[\fB\-\-service\fR \fISERVICE\fR]
[\fB\-\-debug\fR]
[\fB\-\-dev\-tunnel\fR]
[\fB\-\-help\fR]
\fIuser\fR@\fIclient\fR.\fIdomain\fR
[\fISSH_OPTIONS\fR...]
.SH DESCRIPTION
.B wsssh
is a wrapper around the standard SSH client that enables secure tunneling through WebSocket connections. It automatically parses hostnames to extract client IDs and server endpoints, establishes WebSocket tunnels, and launches SSH to connect through the tunnel.
.SH OPTIONS
.TP
.BR \-\-local\-port " \fIPORT\fR"
Local port for tunnel establishment (default: auto-assign)
.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
.B \-\-debug
Enable debug output for troubleshooting
.TP
.B \-\-dev\-tunnel
Development mode: setup tunnel but don't launch SSH
.TP
.B \-\-help
Display help message and exit
.SH HOSTNAME FORMAT
The hostname follows the format: \fIclient_id\fR.\fIwssshd_host\fR[:\fIport\fR]
.TP
Examples:
- \fBremote.example.com\fR → client: \fBremote\fR, server: \fBexample.com:9898\fR
- \fBserver.datacenter.com:2222\fR → client: \fBserver\fR, server: \fBdatacenter.com:2222\fR
.SH EXAMPLES
.TP
Basic SSH connection:
.B wsssh user@myclient.example.com -p 9898
.TP
SSH with custom options:
.B wsssh -p 9898 -i ~/.ssh/key user@myclient.example.com ls -la
.TP
SSH with debug output:
.B wsssh --debug user@remote.example.com -p 2222
.SH ENVIRONMENT
.TP
.B WSSSH_DEBUG
Set to 1 to enable debug output (alternative to --debug)
.SH SECURITY
All tunnel communications are encrypted using SSL/TLS. The client verifies server certificates. For production deployments, use properly signed certificates.
.SH EXIT STATUS
.B wsssh
returns the exit status of the underlying SSH command.
.SH SEE ALSO
.BR ssh (1),
.BR wssshd (1),
.BR wssshc (1),
.BR wsscp (1)
.SH AUTHOR
Written by Stefy Lanza <stefy@nexlab.net>
.SH COPYRIGHT
Copyright \(co 2024 Stefy Lanza and SexHack.me. License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
.SH BUGS
Report bugs to: https://git.nexlab.net/nexlab/wsssh/issues
\ No newline at end of file
/*
* WebSocket SCP (wsscp) - C Implementation
* SCP wrapper that uses WebSocket tunnels through wssshd.
*
* 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 <errno.h>
#include <sys/select.h>
#include "libwsssht/wssshlib.h"
#include "libwsssht/websocket.h"
#include "libwsssht/wssh_ssl.h"
#include "libwsssht/tunnel.h"
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options] [scp_options...] source destination\n", program_name);
fprintf(stderr, "WebSocket SCP Wrapper - SCP through WebSocket tunnels\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, " --dev-tunnel Development mode: setup tunnel but don't launch SCP\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, "\nDonations:\n");
fprintf(stderr, " BTC: bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\n");
fprintf(stderr, " ETH: 0xdA6dAb526515b5cb556d20269207D43fcc760E51\n");
}
int parse_args(int argc, char *argv[], wsscp_config_t *config, int *remaining_argc, char ***remaining_argv) {
// Manually parse arguments to separate wsscp options from SCP options
int scp_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], "--dev-tunnel") == 0) {
config->dev_tunnel = 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, treat as SCP option
scp_start = i;
break;
} else {
// Non-option argument, start of SCP args
scp_start = i;
break;
}
}
// Return remaining arguments (SCP arguments)
*remaining_argc = argc - scp_start;
*remaining_argv = &argv[scp_start];
return 1;
}
int parse_hostname(const char *hostname, char **client_id, char **wssshd_host, const char *config_domain) {
char *colon_pos = strchr(hostname, ':');
if (!colon_pos) {
fprintf(stderr, "Error: Invalid hostname format. Expected user@host: or host:\n");
return 0;
}
char *host_part = strndup(hostname, colon_pos - hostname);
if (!host_part) {
return 0;
}
char *at_pos = strchr(host_part, '@');
char *actual_host;
if (at_pos) {
// Skip user@ part
actual_host = at_pos + 1;
} else {
actual_host = host_part;
}
char *dot_pos = strchr(actual_host, '.');
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_part);
return 0;
}
*client_id = strdup(actual_host);
*wssshd_host = strdup(config_domain);
} else {
*dot_pos = '\0';
*client_id = strdup(actual_host);
*wssshd_host = strdup(dot_pos + 1);
}
free(host_part);
return 1;
}
int parse_scp_args(int argc, char *argv[], char **destination, int debug) {
*destination = NULL;
for (int i = 0; i < argc; i++) {
if (argv[i][0] != '-') {
// Check if this looks like a remote destination (contains @ or :)
if (strchr(argv[i], '@') || strchr(argv[i], ':')) {
*destination = argv[i];
if (debug) {
printf("[DEBUG] Found SCP destination: %s\n", *destination);
}
break;
}
}
}
if (!*destination) {
fprintf(stderr, "Error: Could not determine destination from SCP arguments\n");
return 0;
}
return 1;
}
char **modify_scp_args(int argc, char *argv[], const char *original_host, int local_port, int *new_argc) {
// Allocate space for: scp + -o + StrictHostKeyChecking=no + -o + UserKnownHostsFile=/dev/null + -o + LogLevel ERROR + -P + port + original args + NULL
char **new_args = malloc((argc + 10) * sizeof(char *));
if (!new_args) {
return NULL;
}
int idx = 0;
new_args[idx++] = "scp";
// Add StrictHostKeyChecking=no option
new_args[idx++] = "-o";
new_args[idx++] = "StrictHostKeyChecking=no";
// Add UserKnownHostsFile=/dev/null option
new_args[idx++] = "-o";
new_args[idx++] = "UserKnownHostsFile=/dev/null";
// Add LogLevel ERROR option
new_args[idx++] = "-o";
new_args[idx++] = "LogLevel=ERROR";
// Add port argument
new_args[idx++] = "-P";
char *port_str = malloc(16);
if (!port_str) {
free(new_args);
return NULL;
}
snprintf(port_str, 16, "%d", local_port);
new_args[idx++] = port_str;
for (int i = 0; i < argc; i++) {
// Check if this argument is the original host:path
if (strcmp(argv[i], original_host) == 0) {
// Replace user@host:path with user@localhost:path or host:path with localhost:path
char *colon_pos = strchr(original_host, ':');
if (colon_pos) {
char *host_part = strndup(original_host, colon_pos - original_host);
char *path_part = colon_pos + 1;
char *at_pos = strchr(host_part, '@');
if (at_pos) {
// Has user part
size_t user_len = at_pos - host_part;
char *new_host = malloc(user_len + 11 + strlen(path_part) + 1); // "@localhost:" (11 chars + null)
if (!new_host) {
free(port_str);
free(new_args);
free(host_part);
return NULL;
}
memcpy(new_host, host_part, user_len);
strcpy(new_host + user_len, "@localhost:");
strcpy(new_host + user_len + 11, path_part);
new_args[idx++] = new_host;
} else {
// No user part
char *new_host = malloc(10 + strlen(path_part) + 1); // "localhost:" (10 chars + null)
if (!new_host) {
free(port_str);
free(new_args);
free(host_part);
return NULL;
}
strcpy(new_host, "localhost:");
strcpy(new_host + 10, path_part);
new_args[idx++] = new_host;
}
free(host_part);
continue;
}
}
new_args[idx++] = argv[i];
}
new_args[idx] = NULL;
*new_argc = idx;
return new_args;
}
int main(int argc, char *argv[]) {
// Read config
char *config_domain = read_config_value("domain");
char *config_tunnel = read_config_value("tunnel");
char *config_tunnel_control = read_config_value("tunnel-control");
char *config_service = read_config_value("service");
wsscp_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");
}
// 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 wsscp 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 SCP argument (the target)
if (remaining_argc == 0) {
fprintf(stderr, "Error: No target specified\n");
print_usage(argv[0]);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
// Parse SCP arguments to extract destination
char *scp_destination = NULL;
if (!parse_scp_args(remaining_argc, remaining_argv, &scp_destination, config.debug)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
}
// Parse the SCP destination to extract client_id and wssshd_host
char *client_id = NULL;
char *wssshd_host = NULL;
int wssshd_port = config.wssshd_port; // Use the wssshd port from config
if (!parse_hostname(scp_destination, &client_id, &wssshd_host, config_domain)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
}
if (config.debug) {
printf("[DEBUG - Tunnel] SCP Destination: %s\n", scp_destination);
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);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
if (config.debug) {
printf("[DEBUG - Tunnel] Using local port: %d\n", local_port);
fflush(stdout);
}
// Modify SCP arguments
int new_scp_argc;
char **new_scp_args = modify_scp_args(remaining_argc, remaining_argv, scp_destination, local_port, &new_scp_argc);
if (!new_scp_args) {
fprintf(stderr, "Error: Failed to modify SCP arguments\n");
free(client_id);
free(wssshd_host);
free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
if (config.debug) {
printf("[DEBUG - Tunnel] Modified SCP command:");
for (int i = 0; new_scp_args[i]; i++) {
printf(" %s", new_scp_args[i]);
}
printf("\n");
fflush(stdout);
}
// Parent process: setup tunnel FIRST
if (config.debug) {
printf("[DEBUG - Tunnel] Parent process 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] Initial 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, 1, NULL);
if (listen_sock < 0) {
setup_attempts++;
if (setup_attempts < max_setup_attempts) {
if (config.debug) {
printf("[DEBUG - Tunnel] Initial 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);
for (int i = 1; i < new_scp_argc; i++) {
if (new_scp_args[i] != remaining_argv[i-1]) {
free(new_scp_args[i]);
}
}
free(new_scp_args);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
// Handle dev-tunnel mode: print SCP command and wait for manual execution
if (config.dev_tunnel) {
printf("\n");
printf("========================================\n");
printf(" DEVELOPMENT TUNNEL MODE ENABLED\n");
printf("========================================\n");
printf("Tunnel is ready on localhost:%d\n", local_port);
printf("\n");
printf("SCP Command to execute manually:\n");
printf(" ");
for (int i = 0; new_scp_args[i]; i++) {
printf("%s ", new_scp_args[i]);
}
printf("\n");
printf("\n");
printf("Or connect manually with telnet:\n");
printf(" telnet localhost %d\n", local_port);
printf("\n");
printf("Press Ctrl+C to exit and close the tunnel.\n");
printf("========================================\n");
printf("\n");
// In dev-tunnel mode, we still need to start the forwarding threads
// to handle bidirectional communication, but we don't fork SCP
goto start_forwarding_threads;
}
// NOW fork to run SCP in background (after tunnel is ready)
start_forwarding_threads:
if (config.debug) {
printf("[DEBUG - Tunnel] About to fork SCP process...\n");
fflush(stdout);
}
pid_t pid = -1; // Initialize to avoid uninitialized warning
if (!config.dev_tunnel) {
pid = fork();
if (pid < 0) {
perror("fork failed");
free(client_id);
free(wssshd_host);
free(config.local_port);
free(config.tunnel);
free(config.tunnel_control);
free(config.service);
for (int i = 1; i < new_scp_argc; i++) {
if (new_scp_args[i] != remaining_argv[i-1]) {
free(new_scp_args[i]);
}
}
free(new_scp_args);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
if (pid == 0) {
// Child process: run SCP
if (config.debug) {
printf("[DEBUG - Tunnel] Child process starting SCP with command:");
for (int i = 0; new_scp_args[i]; i++) {
printf(" %s", new_scp_args[i]);
}
printf("\n");
fflush(stdout);
}
execvp("scp", new_scp_args);
// If we reach here, execvp failed
perror("execvp failed");
free(client_id);
free(wssshd_host);
for (int i = 1; i < new_scp_argc; i++) {
if (new_scp_args[i] != remaining_argv[i-1]) {
free(new_scp_args[i]);
}
}
free(new_scp_args);
exit(1);
}
}
// Parent process: accept SCP 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);
active_tunnel->local_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len);
if (active_tunnel->local_sock < 0) {
perror("Local accept failed");
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
waitpid(pid, NULL, 0);
close(listen_sock);
frame_buffer_free(active_tunnel->outgoing_buffer);
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);
for (int i = 1; i < new_scp_argc; i++) {
if (new_scp_args[i] != remaining_argv[i-1]) {
free(new_scp_args[i]);
}
}
free(new_scp_args);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
// Make local socket non-blocking to prevent hangs
int flags = fcntl(active_tunnel->local_sock, F_GETFL, 0);
if (flags != -1) {
fcntl(active_tunnel->local_sock, F_SETFL, flags | O_NONBLOCK);
}
close(listen_sock); // No longer needed
if (config.debug) {
printf("[DEBUG] Local SCP connection accepted! Starting data forwarding...\n");
fflush(stdout);
}
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (!thread_args) {
perror("Memory allocation failed for thread args");
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
waitpid(pid, NULL, 0);
close(active_tunnel->local_sock);
frame_buffer_free(active_tunnel->outgoing_buffer);
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);
for (int i = 1; i < new_scp_argc; i++) {
if (new_scp_args[i] != remaining_argv[i-1]) {
free(new_scp_args[i]);
}
}
free(new_scp_args);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
thread_args->ssl = active_tunnel->ssl; // Need to store SSL in tunnel struct
thread_args->tunnel = active_tunnel; // Pass the 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]; // Quadruple buffer size for accumulation
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;
if (!config.dev_tunnel && pid > 0 && pid != -1) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
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);
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 immediately when local connection breaks
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);
// Kill SCP process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SCP process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
goto cleanup_and_exit;
}
// Also check if the local socket connection is broken by trying to peek at it
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 immediately when local connection breaks
send_tunnel_close(current_ssl, active_tunnel->request_id, config.debug);
pthread_mutex_unlock(&tunnel_mutex);
// Kill SCP process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SCP process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
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; // 0 seconds
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 before breaking
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);
// Kill SCP process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SCP process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
goto cleanup_and_exit;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive during timeout
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;
// Kill SCP process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SCP process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
// Timeout, continue loop
continue;
}
// Read more data if we don't have a complete frame
if (FD_ISSET(ssl_fd, &readfds)) {
// Always try to read more data if there's space, even if we have a complete frame
// This ensures we can accumulate data for very large frames
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Validate SSL connection state before reading
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 operation
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; // 5 second timeout
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; // Timeout, try again
}
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 with retry
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); // Wait 10ms before retry
continue; // Retry the read operation
}
// Print detailed SSL error information for non-transient errors
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 - use 1 second intervals for WebSocket reconnections
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); // Use 1 second for WebSocket reconnections
}
}
}
if (!reconnected) {
if (config.debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n");
fflush(stdout);
}
tunnel_broken = 1;
// Send tunnel_close notification before breaking
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);
// Kill SCP process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SCP process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
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 from accumulated buffer
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 before breaking
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);
// Kill SCP process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SCP process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
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 with same payload
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);
}
// Just acknowledge, no response needed
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
if (config.debug) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// Copy payload to buffer for processing (null-terminate for string operations)
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
} else {
fprintf(stderr, "Payload too large for processing buffer\n");
// Skip this frame
frame_buffer_used = 0;
continue;
}
// Handle message
if (config.debug) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
fflush(stdout);
}
// Handle 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);
}
fprintf(stderr, "Error: Tunnel closed by remote client\n");
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) {
// Move remaining data to front
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
// Entire buffer was consumed
frame_buffer_used = 0;
}
} else {
// Frame not complete yet, continue reading
continue;
}
}
}
if (config.debug) {
printf("[DEBUG - Tunnel] Tunnel loop ended, waiting for process to finish...\n");
fflush(stdout);
}
cleanup_and_exit:
// Wait for SCP to finish
int status;
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
waitpid(pid, &status, 0);
#pragma GCC diagnostic pop
}
if (config.debug) {
printf("[DEBUG - Tunnel] Process finished with status: %d\n", status);
fflush(stdout);
}
// Wait a few seconds before exiting cleanly
if (config.debug) {
printf("[DEBUG - Tunnel] Waiting 3 seconds before exit...\n");
fflush(stdout);
}
sleep(3);
// Cleanup
if (active_tunnel) {
close(active_tunnel->local_sock);
SSL_free(active_tunnel->ssl);
frame_buffer_free(active_tunnel->outgoing_buffer);
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 allocated strings in new_scp_args
for (int i = 0; i < new_scp_argc; i++) {
// Free strings that were allocated with malloc/strdup
if (i == 8) { // The port string
free(new_scp_args[i]);
} else if (strstr(new_scp_args[i], "@localhost")) {
// The user@localhost string (if allocated)
free(new_scp_args[i]);
}
}
free(new_scp_args);
free(config_domain);
free(config_tunnel);
free(config_tunnel_control);
free(config_service);
pthread_mutex_destroy(&tunnel_mutex);
pthread_mutex_destroy(&ssl_mutex);
// Ensure we exit the process
exit(tunnel_broken ? 1 : 0);
}
\ No newline at end of file
/*
* WebSocket SSH (wsssh) - C Implementation
* SSH wrapper that uses WebSocket tunnels through wssshd.
*
* 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 "libwsssht/wssshlib.h"
#include "libwsssht/websocket.h"
#include "libwsssht/wssh_ssl.h"
#include "libwsssht/tunnel.h"
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options] user@client.domain [ssh_options...]\n", program_name);
fprintf(stderr, "WebSocket SSH Wrapper - SSH through WebSocket tunnels\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, " --dev-tunnel Development mode: setup tunnel but don't launch SSH\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, "\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 wsssh options from SSH options
int ssh_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], "--dev-tunnel") == 0) {
config->dev_tunnel = 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, treat as SSH option
ssh_start = i;
break;
} else {
// Non-option argument, start of SSH args
ssh_start = i;
break;
}
}
// Return remaining arguments (SSH arguments)
*remaining_argc = argc - ssh_start;
*remaining_argv = &argv[ssh_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_ssh_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 host
*host = argv[i];
if (debug) {
printf("[DEBUG - Tunnel] Found SSH host: %s\n", *host);
fflush(stdout);
}
break;
}
}
if (!*host) {
fprintf(stderr, "Error: Could not determine target host from SSH arguments\n");
return 0;
}
return 1;
}
char **modify_ssh_args(int argc, char *argv[], const char *original_host, int local_port, int *new_argc) {
// Allocate space for: ssh + -o + StrictHostKeyChecking=no + -o + UserKnownHostsFile=/dev/null + -o + LogLevel ERROR + -p + port + original args + NULL
char **new_args = malloc((argc + 10) * sizeof(char *));
if (!new_args) {
return NULL;
}
int idx = 0;
new_args[idx++] = "ssh";
// Add StrictHostKeyChecking=no option
new_args[idx++] = "-o";
new_args[idx++] = "StrictHostKeyChecking=no";
// Add UserKnownHostsFile=/dev/null option
new_args[idx++] = "-o";
new_args[idx++] = "UserKnownHostsFile=/dev/null";
// Add LogLevel ERROR option
new_args[idx++] = "-o";
new_args[idx++] = "LogLevel=ERROR";
// Add port argument for local tunnel
new_args[idx++] = "-p";
char *port_str = malloc(16);
if (!port_str) {
free(new_args);
return NULL;
}
snprintf(port_str, 16, "%d", local_port);
new_args[idx++] = port_str;
for (int i = 0; i < argc; i++) {
// Check if this argument is the original host
if (strcmp(argv[i], original_host) == 0) {
// Replace user@host with user@localhost or host with localhost
char *at_pos = strchr(original_host, '@');
if (at_pos) {
// Has user part
size_t user_len = at_pos - original_host;
char *new_host = malloc(user_len + 10); // "@localhost"
if (!new_host) {
free(port_str);
free(new_args);
return NULL;
}
memcpy(new_host, original_host, user_len);
strcpy(new_host + user_len, "@localhost");
new_args[idx++] = new_host;
} else {
// No user part
new_args[idx++] = "localhost";
}
continue;
}
new_args[idx++] = argv[i];
}
new_args[idx] = NULL;
*new_argc = idx;
return new_args;
}
int main(int argc, char *argv[]) {
// Read config
char *config_domain = read_config_value("domain");
char *config_tunnel = read_config_value("tunnel");
char *config_tunnel_control = read_config_value("tunnel-control");
char *config_service = read_config_value("service");
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");
}
// 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 wsssh 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 SSH argument (the target)
if (remaining_argc == 0) {
fprintf(stderr, "Error: No target specified\n");
print_usage(argv[0]);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
// Parse SSH arguments to extract host
char *ssh_host = NULL;
if (!parse_ssh_args(remaining_argc, remaining_argv, &ssh_host, config.debug)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
}
// Parse the SSH host to extract client_id and wssshd_host
char *client_id = NULL;
char *wssshd_host = NULL;
int wssshd_port = config.wssshd_port; // Use the wssshd port from config
if (!parse_hostname(ssh_host, &client_id, &wssshd_host, config_domain)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
}
if (config.debug) {
printf("[DEBUG - Tunnel] SSH Host: %s\n", ssh_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);
}
// Modify SSH arguments
int new_ssh_argc;
char **new_ssh_args = modify_ssh_args(remaining_argc, remaining_argv, ssh_host, local_port, &new_ssh_argc);
if (!new_ssh_args) {
fprintf(stderr, "Error: Failed to modify SSH arguments\n");
free(client_id);
free(wssshd_host);
free(config.local_port);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
if (config.debug) {
printf("[DEBUG - Tunnel] Modified SSH command:");
for (int i = 0; new_ssh_args[i]; i++) {
printf(" %s", new_ssh_args[i]);
}
printf("\n");
fflush(stdout);
}
// Parent process: setup tunnel FIRST
if (config.debug) {
printf("[DEBUG - Tunnel] Parent process 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] Initial 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, NULL);
if (listen_sock < 0) {
setup_attempts++;
if (setup_attempts < max_setup_attempts) {
if (config.debug) {
printf("[DEBUG - Tunnel] Initial 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);
for (int i = 1; i < new_ssh_argc; i++) {
if (new_ssh_args[i] != remaining_argv[i-1]) {
free(new_ssh_args[i]);
}
}
free(new_ssh_args);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
// Handle dev-tunnel mode: print SSH command and wait for manual execution
if (config.dev_tunnel) {
printf("\n");
printf("========================================\n");
printf(" DEVELOPMENT TUNNEL MODE ENABLED\n");
printf("========================================\n");
printf("Tunnel is ready on localhost:%d\n", local_port);
printf("\n");
printf("SSH Command to execute manually:\n");
printf(" ");
for (int i = 0; new_ssh_args[i]; i++) {
printf("%s ", new_ssh_args[i]);
}
printf("\n");
printf("\n");
printf("Or connect manually with telnet:\n");
printf(" telnet localhost %d\n", local_port);
printf("\n");
printf("Press Ctrl+C to exit and close the tunnel.\n");
printf("========================================\n");
printf("\n");
// In dev-tunnel mode, we still need to start the forwarding threads
// to handle bidirectional communication, but we don't fork SSH
goto start_forwarding_threads;
}
// NOW fork to run SSH in background (after tunnel is ready)
if (config.debug) {
printf("[DEBUG - Tunnel] About to fork SSH process...\n");
fflush(stdout);
}
pid_t pid = -1; // Initialize to avoid uninitialized warning
if (!config.dev_tunnel) {
pid = fork();
if (pid < 0) {
perror("fork failed");
free(client_id);
free(wssshd_host);
free(config.local_port);
for (int i = 1; i < new_ssh_argc; i++) {
if (new_ssh_args[i] != remaining_argv[i-1]) {
free(new_ssh_args[i]);
}
}
free(new_ssh_args);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
if (pid == 0) {
// Child process: run SSH
if (config.debug) {
printf("[DEBUG - Tunnel] Child process starting SSH with command:");
for (int i = 0; new_ssh_args[i]; i++) {
printf(" %s", new_ssh_args[i]);
}
printf("\n");
fflush(stdout);
}
execvp("ssh", new_ssh_args);
// If we reach here, execvp failed
perror("execvp failed");
free(client_id);
free(wssshd_host);
for (int i = 1; i < new_ssh_argc; i++) {
if (new_ssh_args[i] != remaining_argv[i-1]) {
free(new_ssh_args[i]);
}
}
free(new_ssh_args);
exit(1);
}
}
start_forwarding_threads:
// Parent process: accept SSH 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");
if (!config.dev_tunnel) {
kill(pid, SIGTERM);
waitpid(pid, NULL, 0);
}
close(listen_sock);
free(active_tunnel);
active_tunnel = NULL;
free(client_id);
free(wssshd_host);
free(config.local_port);
for (int i = 1; i < new_ssh_argc; i++) {
if (new_ssh_args[i] != remaining_argv[i-1]) {
free(new_ssh_args[i]);
}
}
free(new_ssh_args);
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 SSH 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 SSH 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 SSH client\n", sent);
fflush(stdout);
}
}
}
pthread_mutex_unlock(&tunnel_mutex);
if (config.debug) {
printf("[DEBUG - Tunnel] Local SSH 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");
if (!config.dev_tunnel) {
kill(pid, SIGTERM);
waitpid(pid, NULL, 0);
}
// Close socket with mutex protection
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && active_tunnel->local_sock >= 0) {
close(active_tunnel->local_sock);
}
pthread_mutex_unlock(&tunnel_mutex);
free(active_tunnel);
active_tunnel = NULL;
free(client_id);
free(wssshd_host);
free(config.local_port);
for (int i = 1; i < new_ssh_argc; i++) {
if (new_ssh_args[i] != remaining_argv[i-1]) {
free(new_ssh_args[i]);
}
}
free(new_ssh_args);
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
thread_args->ssl = current_ssl; // Use the current SSL connection
thread_args->tunnel = active_tunnel; // Pass the 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]; // Quadruple buffer size for accumulation
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;
if (!config.dev_tunnel && pid > 0 && pid != -1) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
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 immediately when local connection breaks
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);
// Kill SSH process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SSH process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
goto cleanup_and_exit;
}
// Also check if the local socket connection is broken by trying to peek at it
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 immediately when local connection breaks
send_tunnel_close(current_ssl, active_tunnel->request_id, config.debug);
pthread_mutex_unlock(&tunnel_mutex);
// Kill SSH process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SSH process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
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; // 0 seconds
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 before breaking
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);
// Kill SSH process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SSH process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
goto cleanup_and_exit;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive during timeout
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;
// Kill SSH process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SSH process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
// Timeout, continue loop
continue;
}
// Read more data if we don't have a complete frame
if (FD_ISSET(ssl_fd, &readfds)) {
// Always try to read more data if there's space, even if we have a complete frame
// This ensures we can accumulate data for very large frames
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Validate SSL connection state before reading
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 operation
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; // 5 second timeout
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; // Timeout, try again
}
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 with retry
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); // Wait 10ms before retry
continue; // Retry the read operation
}
// Print detailed SSL error information for non-transient errors
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 - use 1 second intervals for WebSocket reconnections
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); // Use 1 second for WebSocket reconnections
}
}
}
if (!reconnected) {
if (config.debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n");
fflush(stdout);
}
tunnel_broken = 1;
// Send tunnel_close notification before breaking
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);
// Kill SSH process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SSH process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
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 from accumulated buffer
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 before breaking
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);
// Kill SSH process and exit immediately
if (config.debug) {
printf("[DEBUG - Tunnel] Killing SSH process and exiting due to broken tunnel\n");
fflush(stdout);
}
if (!config.dev_tunnel) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
kill(pid, SIGTERM);
#pragma GCC diagnostic pop
}
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 with same payload
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);
}
// Just acknowledge, no response needed
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
if (config.debug) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// Copy payload to buffer for processing (null-terminate for string operations)
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
} else {
fprintf(stderr, "Payload too large for processing buffer\n");
// Skip this frame
frame_buffer_used = 0;
continue;
}
// Handle message
if (config.debug) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
fflush(stdout);
}
// Handle 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) {
// Move remaining data to front
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
// Entire buffer was consumed
frame_buffer_used = 0;
}
} else {
// Frame not complete yet, continue reading
continue;
}
}
}
cleanup_and_exit:
// Cleanup section - reached either normally or via goto
if (config.debug) {
printf("[DEBUG - Tunnel] Performing cleanup and exiting\n");
fflush(stdout);
}
// If tunnel was broken, wait a moment for SSH process to terminate
if (tunnel_broken) {
if (config.debug) {
printf("[DEBUG - Tunnel] Waiting briefly for SSH process to terminate\n");
fflush(stdout);
}
// Give SSH process a moment to terminate gracefully
usleep(100000); // 100ms
// Check if SSH process is still running
int status;
pid_t result = waitpid(pid, &status, WNOHANG);
if (result == 0) {
if (config.debug) {
printf("[DEBUG - Tunnel] SSH process still running, sending SIGKILL\n");
fflush(stdout);
}
// SSH process is still running, force kill it
if (!config.dev_tunnel) {
kill(pid, SIGKILL);
}
// Wait for it to actually terminate
waitpid(pid, &status, 0);
} else if (result > 0) {
if (config.debug) {
printf("[DEBUG - Tunnel] SSH process terminated with status: %d\n", status);
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 allocated strings in new_ssh_args
for (int i = 0; i < new_ssh_argc; i++) {
// Free strings that were allocated with malloc/strdup
if (i == 8) { // The port string
free(new_ssh_args[i]);
} else if (strstr(new_ssh_args[i], "@localhost")) {
// The user@localhost string (if allocated)
free(new_ssh_args[i]);
}
}
free(new_ssh_args);
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);
}
// Ensure we exit the process
exit(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