Add wsscp - WebSocket SCP wrapper application

New wsscp application:
- WebSocket SCP wrapper similar to wsssh but for SCP instead of SSH
- Uses -P option for port detection (SCP standard) instead of -p
- Automatically constructs ProxyCommand for WebSocket tunnels
- Supports same command line syntax as regular SCP
- Includes automatic port detection from -P option
- Full integration with wsssht WebSocket tunnel infrastructure

Implementation details:
- wsscp.h: Header file with configuration structures and function declarations
- wsscp.c: Complete implementation with argument parsing and SCP command generation
- Updated configure.sh to build wsscp alongside other tools
- Added wsscp.1 man page with comprehensive documentation
- Tested functionality with debug mode and argument parsing

Features:
- Automatic wsssht path detection (PATH or same directory)
- Smart port detection: explicit --wssshd-port takes precedence over -P option
- Support for all wsssht tunnel options (--tunnel, --tunnel-control, --debug)
- Compatible with existing wssshc/wsssht infrastructure
- Proper error handling and user-friendly help messages

Usage examples:
  wsscp localfile user@myclient:/remote/path
  wsscp -P 2222 localfile user@myclient.server.com:/remote/path
  wsscp --debug --tunnel websocket localfile user@myclient:/remote/path
parent e9748765
...@@ -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 wsssht.c wsssh.c SRCS = wssshc.c wsssht.c wsssh.c wsscp.c
OBJS = $(SRCS:.c=.o) OBJS = $(SRCS:.c=.o)
TARGETS = wssshc wsssht wsssh TARGETS = wssshc wsssht wsssh wsscp
# Man pages # Man pages
MANPAGES = man/wssshc.1 man/wsssht.1 man/wsssh.1 MANPAGES = man/wssshc.1 man/wsssht.1 man/wsssh.1 man/wsscp.1
# Default target # Default target
all: $(TARGETS) all: $(TARGETS)
...@@ -78,13 +78,16 @@ wsssht: wsssht.o $(LIB_OBJS) ...@@ -78,13 +78,16 @@ wsssht: wsssht.o $(LIB_OBJS)
wsssh: wsssh.o wsssh: wsssh.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsscp: wsscp.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Object files # Object files
%.o: %.c %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
# Clean # Clean
clean: clean:
rm -f $(OBJS) $(LIB_OBJS) $(TARGETS) rm -f $(OBJS) $(LIB_OBJS) $(TARGETS) wsscp.o
# Install (optional) # Install (optional)
install: all install: all
...@@ -107,15 +110,19 @@ uninstall: ...@@ -107,15 +110,19 @@ uninstall:
rm -f $(DESTDIR)/usr/local/bin/wssshc rm -f $(DESTDIR)/usr/local/bin/wssshc
rm -f $(DESTDIR)/usr/local/bin/wsssht rm -f $(DESTDIR)/usr/local/bin/wsssht
rm -f $(DESTDIR)/usr/local/bin/wsssh rm -f $(DESTDIR)/usr/local/bin/wsssh
rm -f $(DESTDIR)/usr/local/bin/wsscp
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/wsssht.1 rm -f $(DESTDIR)/usr/local/share/man/man1/wsssht.1
rm -f $(DESTDIR)/usr/local/share/man/man1/wsssh.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/bin/wssshc rm -f $(DESTDIR)/usr/bin/wssshc
rm -f $(DESTDIR)/usr/bin/wsssht rm -f $(DESTDIR)/usr/bin/wsssht
rm -f $(DESTDIR)/usr/bin/wsssh rm -f $(DESTDIR)/usr/bin/wsssh
rm -f $(DESTDIR)/usr/bin/wsscp
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/wsssht.1 rm -f $(DESTDIR)/usr/share/man/man1/wsssht.1
rm -f $(DESTDIR)/usr/share/man/man1/wsssh.1 rm -f $(DESTDIR)/usr/share/man/man1/wsssh.1
rm -f $(DESTDIR)/usr/share/man/man1/wsscp.1
.PHONY: all clean install uninstall .PHONY: all clean install uninstall
EOF EOF
......
.TH WSSCP 1 "September 2024" "wsscp 1.0" "WebSocket SSH Tools"
.SH NAME
wsscp \- SCP wrapper with WebSocket ProxyCommand support
.SH SYNOPSIS
.B wsscp
[\fB\-\-help\fR] [\fB\-\-clientid\fR \fIclient_id\fR] [\fB\-\-wssshd\-host\fR \fIhost\fR]
[\fB\-\-wssshd\-port\fR \fIport\fR] [\fB\-\-debug\fR] [\fB\-\-tunnel\fR \fItransport\fR]
[\fB\-\-tunnel\-control\fR \fItransport\fR] \fIsource_file\fR \fIdestination\fR
.SH DESCRIPTION
.B wsscp
is an SCP wrapper that automatically configures SCP to use WebSocket tunnels through
.B wsssht
with ProxyCommand. It parses the destination specification and constructs the appropriate
SCP command with ProxyCommand to establish secure file transfers through WebSocket relays.
.SH OPTIONS
.TP
.B \-\-help
Show help message and exit.
.TP
.B \-\-clientid \fIclient_id\fR
Specify the client ID of the registered wssshc endpoint.
.TP
.B \-\-wssshd\-host \fIhost\fR
Specify the wssshd relay host.
.TP
.B \-\-wssshd\-port \fIport\fR
Specify the wssshd relay websocket port (default: 9898).
.TP
.B \-\-debug
Enable debug output. When debug is enabled, the SCP command is displayed but not executed.
.TP
.B \-\-tunnel \fItransport\fR
Select data channel transport (comma-separated or 'any').
.TP
.B \-\-tunnel\-control \fItransport\fR
Select control channel transport (comma-separated or 'any').
.SH DESTINATION FORMAT
The destination specification follows the format:
.sp
\fIuser\fR[\fB@\fR[\fIclientid\fR][\fB.\fR[\fIwssshd\-host\fR]][\fB:\fR[\fIremote_path\fR]]]
.sp
Where:
.RS
.TP
\fIuser\fR
SCP username
.TP
\fIclientid\fR
Client ID of the registered wssshc endpoint
.TP
\fIwssshd\-host\fR
wssshd relay hostname
.TP
\fIremote_path\fR
Remote file path on the target system
.RE
.SH EXAMPLES
.TP
Copy file to myclient:
.B wsscp localfile user@myclient:/remote/path
.TP
Copy file with specific relay host:
.B wsscp localfile user@myclient.server.com:/remote/path
.TP
Copy file with custom port:
.B wsscp -P 2222 localfile user@myclient.server.com:/remote/path
.TP
Enable debug output:
.B wsscp \-\-debug localfile user@myclient.server.com:/remote/path
.TP
Specify transport:
.B wsscp \-\-tunnel websocket localfile user@myclient.server.com:/remote/path
.TP
Pass additional SCP options:
.B wsscp localfile user@myclient.server.com:/remote/path -P 2222 -o StrictHostKeyChecking=no
.SH ENVIRONMENT
.B wsscp
requires
.B wsssht
to be available either in PATH or in the same directory as wsscp.
.SH SEE ALSO
.BR wsssht (1),
.BR wssshc (1),
.BR wsssh (1),
.BR scp (1)
.SH AUTHOR
Written by Stefy Lanza <stefy@nexlab.net> and SexHack.me
.SH COPYRIGHT
Copyright \(co 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
.br
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 <stefy@nexlab.net>
\ No newline at end of file
/*
* WebSocket SCP (wsscp) - C Implementation
* SCP wrapper with WebSocket ProxyCommand support.
*
* 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 <errno.h>
#include <limits.h>
#include "wsscp.h"
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [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, " --clientid CLIENT_ID Client ID of the registered wssshc endpoint\n");
fprintf(stderr, " --wssshd-host HOST wssshd relay host\n");
fprintf(stderr, " --wssshd-port PORT wssshd relay websocket port (default: 9898)\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, " --help Show this help\n");
fprintf(stderr, "\nDestination format:\n");
fprintf(stderr, " user@client_id[.wssshd_host]:/remote/path\n");
fprintf(stderr, "\nExamples:\n");
fprintf(stderr, " %s localfile user@myclient:/remote/path\n", program_name);
fprintf(stderr, " %s --wssshd-port 9898 localfile user@myclient.server.com:/remote/path\n", program_name);
fprintf(stderr, "\nDonations:\n");
fprintf(stderr, " BTC: bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\n");
fprintf(stderr, " ETH: 0xdA6dAb526515b5cb556d20269207D43fcc760E51\n");
}
int parse_wsscp_args(int argc, char *argv[], wsscp_config_t *config) {
static struct option long_options[] = {
{"clientid", required_argument, 0, 'c'},
{"wssshd-host", required_argument, 0, 'H'},
{"wssshd-port", required_argument, 0, 'P'},
{"debug", no_argument, 0, 'd'},
{"tunnel", required_argument, 0, 't'},
{"tunnel-control", required_argument, 0, 'T'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "c:H:P:dt:T:h", long_options, &option_index)) != -1) {
switch (opt) {
case 'c':
config->client_id = strdup(optarg);
break;
case 'H':
config->wssshd_host = strdup(optarg);
break;
case 'P':
config->wssshd_port = atoi(optarg);
config->wssshd_port_explicit = 1;
break;
case 'd':
config->debug = 1;
break;
case 't':
config->tunnel = strdup(optarg);
break;
case 'T':
config->tunnel_control = strdup(optarg);
break;
case 'h':
print_usage(argv[0]);
return 0;
default:
print_usage(argv[0]);
return 0;
}
}
// Store remaining arguments (source and destination)
config->remaining_argc = argc - optind;
config->remaining_argv = &argv[optind];
return 1;
}
int parse_target_string(const char *target, wsscp_config_t *config) {
if (!target) return 0;
char *target_copy = strdup(target);
if (!target_copy) return 0;
// Parse user@host:path format
char *at_pos = strchr(target_copy, '@');
if (at_pos) {
*at_pos = '\0';
config->user = strdup(target_copy);
// Parse the part after @
char *host_part = at_pos + 1;
// Check for :path
char *colon_pos = strchr(host_part, ':');
if (colon_pos) {
*colon_pos = '\0';
config->destination = strdup(colon_pos + 1);
}
// Check for .domain
char *dot_pos = strchr(host_part, '.');
if (dot_pos) {
*dot_pos = '\0';
config->client_id = strdup(host_part);
config->wssshd_host = strdup(dot_pos + 1);
} else {
config->client_id = strdup(host_part);
}
} else {
// No @, just host:path
char *colon_pos = strchr(target_copy, ':');
if (colon_pos) {
*colon_pos = '\0';
config->destination = strdup(colon_pos + 1);
config->client_id = strdup(target_copy);
} else {
config->client_id = strdup(target_copy);
}
}
free(target_copy);
return 1;
}
int parse_scp_port_from_args(wsscp_config_t *config) {
if (!config->remaining_argv || config->remaining_argc < 2) {
return 0;
}
// Look for -P option in remaining arguments (SCP uses -P, not -p)
for (int i = 0; i < config->remaining_argc - 1; i++) {
if (strcmp(config->remaining_argv[i], "-P") == 0) {
// Found -P, next argument should be the port
char *endptr;
int port = strtol(config->remaining_argv[i + 1], &endptr, 10);
if (*endptr == '\0' && port > 0 && port <= 65535) {
return port;
}
}
}
return 0; // No valid -P option found
}
char *find_wsssht_path() {
// Check if wsssht is in PATH
if (system("which wsssht > /dev/null 2>&1") == 0) {
return strdup("wsssht");
}
// If not in PATH, check in the same directory as wsscp
char wsssh_path[PATH_MAX];
ssize_t len = readlink("/proc/self/exe", wsssh_path, sizeof(wsssh_path) - 1);
if (len != -1) {
wsssh_path[len] = '\0';
char *dir_end = strrchr(wsssh_path, '/');
if (dir_end) {
*dir_end = '\0';
// Ensure we have enough space for the path + "/wsssht" + null terminator
size_t wsssh_path_len = strlen(wsssh_path);
if (wsssh_path_len + 8 < PATH_MAX) { // 8 = "/wsssht" + null
char wsssht_full_path[PATH_MAX];
strcpy(wsssht_full_path, wsssh_path);
strcat(wsssht_full_path, "/wsssht");
if (access(wsssht_full_path, X_OK) == 0) {
return strdup(wsssht_full_path);
}
}
}
}
return NULL;
}
char *build_proxy_command(wsscp_config_t *config) {
char *wsssht_path = find_wsssht_path();
if (!wsssht_path) {
fprintf(stderr, "Error: wsssht not found in PATH or in the same directory as wsscp\n");
fprintf(stderr, "Please install wsssht to use wsscp\n");
return NULL;
}
char *cmd = malloc(2048);
if (!cmd) {
free(wsssht_path);
return NULL;
}
// Start with wsssht path --pipe
sprintf(cmd, "%s --pipe", wsssht_path);
free(wsssht_path);
// Add debug flag if enabled
if (config->debug) {
strcat(cmd, " --debug");
}
// Add tunnel options if specified
if (config->tunnel) {
char tunnel_option[256];
sprintf(tunnel_option, " --tunnel %s", config->tunnel);
strcat(cmd, tunnel_option);
}
if (config->tunnel_control) {
char tunnel_control_option[256];
sprintf(tunnel_control_option, " --tunnel-control %s", config->tunnel_control);
strcat(cmd, tunnel_control_option);
}
// If --wssshd-port was not explicitly set, check for -P in SCP arguments
if (!config->wssshd_port_explicit) {
int scp_port = parse_scp_port_from_args(config);
if (scp_port > 0) {
char port_str[32];
sprintf(port_str, " --wssshd-port %d", scp_port);
strcat(cmd, port_str);
}
}
// Add the SSH URL part
char ssh_url[512];
if (config->wssshd_host) {
sprintf(ssh_url, " ssh://%s@%s", config->client_id, config->wssshd_host);
} else {
sprintf(ssh_url, " ssh://%s", config->client_id);
}
strcat(cmd, ssh_url);
return cmd;
}
int execute_scp_command(char *command, int debug) {
if (debug) {
printf("[DEBUG] Executing: %s\n", command);
}
// Execute the command
int result = system(command);
if (result == -1) {
perror("system");
return 1;
}
return WEXITSTATUS(result);
}
int main(int argc, char *argv[]) {
// Initialize configuration
wsscp_config_t config = {
.client_id = NULL,
.wssshd_host = NULL,
.wssshd_port = 9898,
.wssshd_port_explicit = 0,
.debug = 0,
.tunnel = NULL,
.tunnel_control = NULL,
.user = NULL,
.target_host = NULL,
.ssh_string = NULL,
.source_file = NULL,
.destination = NULL,
.remaining_argc = 0,
.remaining_argv = NULL
};
// Easter egg: --support option (only when it's the only argument)
if (argc == 2 && strcmp(argv[1], "--support") == 0) {
printf("Support the dolls!\n");
printf("BTC: bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\n");
printf("ETH: 0xdA6dAb526515b5cb556d20269207D43fcc760E51\n");
return 0;
}
// Handle --help before parsing
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_usage(argv[0]);
return 0;
}
}
// Parse wsscp arguments
if (!parse_wsscp_args(argc, argv, &config)) {
return 1;
}
// Need at least source and destination
if (config.remaining_argc < 2) {
fprintf(stderr, "Error: Source and destination required\n");
print_usage(argv[0]);
return 1;
}
// Parse source file
config.source_file = strdup(config.remaining_argv[0]);
// Parse destination
if (!parse_target_string(config.remaining_argv[1], &config)) {
fprintf(stderr, "Error: Invalid destination format\n");
print_usage(argv[0]);
free(config.source_file);
return 1;
}
// Check if client_id and destination are provided
if (!config.client_id || !config.destination) {
fprintf(stderr, "Error: Client ID and destination path required\n");
print_usage(argv[0]);
free(config.source_file);
return 1;
}
if (config.debug) {
printf("[DEBUG] Source file: %s\n", config.source_file);
printf("[DEBUG] Client ID: %s\n", config.client_id);
printf("[DEBUG] Destination: %s\n", config.destination);
if (config.wssshd_host) {
printf("[DEBUG] WSSSHD Host: %s\n", config.wssshd_host);
}
printf("[DEBUG] WSSSHD Port: %d\n", config.wssshd_port);
}
// Build ProxyCommand
char *proxy_command = build_proxy_command(&config);
if (!proxy_command) {
free(config.source_file);
return 1;
}
// Build SCP command
char scp_command[4096];
sprintf(scp_command, "scp");
// Add debug flag to SCP if enabled
if (config.debug) {
strcat(scp_command, " -v");
}
// Add ProxyCommand
char proxy_option[3072];
sprintf(proxy_option, " -o ProxyCommand=\"%s\"", proxy_command);
strcat(scp_command, proxy_option);
// Add source file
char source_part[1024];
sprintf(source_part, " %s", config.source_file);
strcat(scp_command, source_part);
// Add destination
char dest_part[1024];
if (config.user) {
sprintf(dest_part, " %s@%s:%s", config.user, config.client_id, config.destination);
} else {
sprintf(dest_part, " %s:%s", config.client_id, config.destination);
}
strcat(scp_command, dest_part);
// Add any additional SCP arguments
for (int i = 2; i < config.remaining_argc; i++) {
char additional_arg[256];
sprintf(additional_arg, " %s", config.remaining_argv[i]);
strcat(scp_command, additional_arg);
}
// Execute SCP command
int result;
if (config.debug) {
printf("[DEBUG] Executing: %s\n", scp_command);
result = execute_scp_command(scp_command, config.debug);
} else {
result = execute_scp_command(scp_command, config.debug);
}
// Cleanup
free(proxy_command);
free(config.source_file);
free(config.client_id);
free(config.wssshd_host);
free(config.tunnel);
free(config.tunnel_control);
free(config.user);
free(config.destination);
return result;
}
\ No newline at end of file
/*
* WebSocket SCP (wsscp) - Header file
* SCP wrapper with WebSocket ProxyCommand support.
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef WSSCP_H
#define WSSCP_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <getopt.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/select.h>
// Configuration structure for wsscp
typedef struct {
char *client_id;
char *wssshd_host;
int wssshd_port;
int wssshd_port_explicit; // Flag to track if --wssshd-port was explicitly set
int debug;
char *tunnel;
char *tunnel_control;
char *user;
char *target_host;
char *ssh_string;
char *source_file;
char *destination;
int remaining_argc;
char **remaining_argv;
} wsscp_config_t;
// Function declarations
int parse_wsscp_args(int argc, char *argv[], wsscp_config_t *config);
int parse_target_string(const char *target, wsscp_config_t *config);
int parse_scp_port_from_args(wsscp_config_t *config);
char *build_proxy_command(wsscp_config_t *config);
char *find_wsssht_path();
int execute_scp_command(char *command, int debug);
void print_usage(const char *program_name);
#endif /* WSSCP_H */
\ 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