Commit d803985a authored by nextime's avatar nextime

Remove "clean" duplicate

parent b77037ab
#!/bin/bash
# WebSocket SSH Tools Clean Script
# Clean script for removing build artifacts from WebSocket SSH tools
#
# 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/>.
# Remove PyInstaller build artifacts
rm -rf build/
rm -rf dist/
rm -f *.spec
# Remove C version build artifacts
if [ -d "wssshtools" ]; then
cd wssshtools
make clean 2>/dev/null || true
rm -f Makefile
rm -f configure.sh.stamp
rm -f man/*.1.gz 2>/dev/null || true
cd ..
fi
# Remove Debian packaging artifacts
rm -f dist/wsssh-tools*.deb
rm -f dist/wsssh-tools*.dsc
rm -f dist/wsssh-tools*.tar.gz
rm -f dist/wsssh-tools*.changes
rm -f dist/wsssh-tools*.buildinfo
rm -f wsssh-tools*.deb
rm -f wsssh-tools*.dsc
rm -f wsssh-tools*.tar.gz
rm -f wsssh-tools*.changes
rm -f wsssh-tools*.buildinfo
# Remove Debian build directory and artifacts
if [ -d "wssshtools" ]; then
rm -rf wssshtools/debian/wsssh-tools/
rm -f wssshtools/debian/files
rm -f wssshtools/debian/*.debhelper*
rm -f wssshtools/debian/*.substvars
rm -f wssshtools/debian/debhelper-build-stamp
fi
# Optionally remove SSL certificates (uncomment if needed)
# rm -f cert.pem key.pem
echo "Clean complete. Build artifacts removed."
echo "Note: SSL certificates (cert.pem, key.pem) preserved. Uncomment lines in clean.sh to remove them."
\ No newline at end of file
logos/banner-800x200.png

52.8 KB | W: | H:

logos/banner-800x200.png

52.7 KB | W: | H:

logos/banner-800x200.png
logos/banner-800x200.png
logos/banner-800x200.png
logos/banner-800x200.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-128.png

23.3 KB | W: | H:

logos/icon-128.png

23.2 KB | W: | H:

logos/icon-128.png
logos/icon-128.png
logos/icon-128.png
logos/icon-128.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-16.png

2.43 KB | W: | H:

logos/icon-16.png

2.34 KB | W: | H:

logos/icon-16.png
logos/icon-16.png
logos/icon-16.png
logos/icon-16.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-256.png

84.7 KB | W: | H:

logos/icon-256.png

84.6 KB | W: | H:

logos/icon-256.png
logos/icon-256.png
logos/icon-256.png
logos/icon-256.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-32.png

3.33 KB | W: | H:

logos/icon-32.png

3.24 KB | W: | H:

logos/icon-32.png
logos/icon-32.png
logos/icon-32.png
logos/icon-32.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-48.png

5.01 KB | W: | H:

logos/icon-48.png

4.91 KB | W: | H:

logos/icon-48.png
logos/icon-48.png
logos/icon-48.png
logos/icon-48.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-64.png

7.33 KB | W: | H:

logos/icon-64.png

7.23 KB | W: | H:

logos/icon-64.png
logos/icon-64.png
logos/icon-64.png
logos/icon-64.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-128.png

23.3 KB | W: | H:

logos/logo-128.png

23.2 KB | W: | H:

logos/logo-128.png
logos/logo-128.png
logos/logo-128.png
logos/logo-128.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-256.png

84.7 KB | W: | H:

logos/logo-256.png

84.6 KB | W: | H:

logos/logo-256.png
logos/logo-256.png
logos/logo-256.png
logos/logo-256.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-512.png

307 KB | W: | H:

logos/logo-512.png

307 KB | W: | H:

logos/logo-512.png
logos/logo-512.png
logos/logo-512.png
logos/logo-512.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-64.png

7.33 KB | W: | H:

logos/logo-64.png

7.23 KB | W: | H:

logos/logo-64.png
logos/logo-64.png
logos/logo-64.png
logos/logo-64.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-high-quality.png

778 KB | W: | H:

logos/logo-high-quality.png

778 KB | W: | H:

logos/logo-high-quality.png
logos/logo-high-quality.png
logos/logo-high-quality.png
logos/logo-high-quality.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -40,7 +40,7 @@ from flask_login import LoginManager, UserMixin, login_user, login_required, log ...@@ -40,7 +40,7 @@ from flask_login import LoginManager, UserMixin, login_user, login_required, log
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
# Client registry: id -> websocket # Client registry: id -> {'websocket': ws, 'last_seen': timestamp, 'status': 'active'|'disconnected'}
clients = {} clients = {}
# Active tunnels: request_id -> {'client_ws': ws, 'wsssh_ws': ws, 'client_id': id} # Active tunnels: request_id -> {'client_ws': ws, 'wsssh_ws': ws, 'client_id': id}
active_tunnels = {} active_tunnels = {}
...@@ -49,6 +49,7 @@ active_terminals = {} ...@@ -49,6 +49,7 @@ active_terminals = {}
debug = False debug = False
server_password = None server_password = None
args = None args = None
import time
# Flask app for web interface # Flask app for web interface
app = Flask(__name__) app = Flask(__name__)
...@@ -73,6 +74,20 @@ class User(UserMixin, db.Model): ...@@ -73,6 +74,20 @@ class User(UserMixin, db.Model):
def load_user(user_id): def load_user(user_id):
return db.session.get(User, int(user_id)) return db.session.get(User, int(user_id))
def cleanup_expired_clients():
"""Remove clients that have been disconnected for more than 30 seconds"""
current_time = time.time()
expired_clients = []
for client_id, client_info in clients.items():
if client_info['status'] == 'disconnected':
if current_time - client_info['last_seen'] > 30:
expired_clients.append(client_id)
if debug: print(f"[DEBUG] [WebSocket] Client {client_id} expired and removed")
for client_id in expired_clients:
del clients[client_id]
def openpty_with_fallback(): def openpty_with_fallback():
"""Open a PTY with fallback to different device paths for systems where /dev/pty doesn't exist""" """Open a PTY with fallback to different device paths for systems where /dev/pty doesn't exist"""
# First try the standard pty.openpty() # First try the standard pty.openpty()
...@@ -152,10 +167,17 @@ with app.app_context(): ...@@ -152,10 +167,17 @@ with app.app_context():
@login_required @login_required
def index(): def index():
global args global args
# Get client information with status
client_info = {}
for client_id, client_data in clients.items():
client_info[client_id] = {
'status': client_data['status'],
'last_seen': client_data['last_seen']
}
return render_template('index.html', return render_template('index.html',
clients=list(clients.keys()), clients=client_info,
websocket_port=args.port, websocket_port=args.port,
domain=args.domain) domain=args.domain)
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
...@@ -241,8 +263,14 @@ def terminal(client_id): ...@@ -241,8 +263,14 @@ def terminal(client_id):
@app.route('/api/clients') @app.route('/api/clients')
@login_required @login_required
def get_clients(): def get_clients():
client_info = {}
for client_id, client_data in clients.items():
client_info[client_id] = {
'status': client_data['status'],
'last_seen': client_data['last_seen']
}
return jsonify({ return jsonify({
'clients': list(clients.keys()), 'clients': client_info,
'count': len(clients) 'count': len(clients)
}) })
...@@ -377,30 +405,44 @@ def disconnect_terminal(client_id): ...@@ -377,30 +405,44 @@ def disconnect_terminal(client_id):
async def handle_websocket(websocket, path=None): async def handle_websocket(websocket, path=None):
try: try:
async for message in websocket: async for message in websocket:
if debug: print(f"[DEBUG] WebSocket message received: {message[:100]}...") if debug: print(f"[DEBUG] [WebSocket] Message received: {message[:100]}...")
data = json.loads(message) data = json.loads(message)
if data.get('type') == 'register': if data.get('type') == 'register':
client_id = data['id'] client_id = data['id']
client_password = data.get('password', '') client_password = data.get('password', '')
if client_password == server_password: if client_password == server_password:
clients[client_id] = websocket # Check if client was previously disconnected
print(f"Client {client_id} registered") was_disconnected = False
if client_id in clients and clients[client_id]['status'] == 'disconnected':
was_disconnected = True
print(f"[DEBUG] [WebSocket] Client {client_id} reconnecting (was disconnected)")
clients[client_id] = {
'websocket': websocket,
'last_seen': time.time(),
'status': 'active'
}
if was_disconnected:
print(f"Client {client_id} reconnected")
else:
print(f"Client {client_id} registered")
await websocket.send(json.dumps({"type": "registered", "id": client_id})) await websocket.send(json.dumps({"type": "registered", "id": client_id}))
else: else:
print(f"Client {client_id} registration failed: invalid password") print(f"[DEBUG] [WebSocket] Client {client_id} registration failed: invalid password")
await websocket.send(json.dumps({"type": "registration_error", "error": "Invalid password"})) await websocket.send(json.dumps({"type": "registration_error", "error": "Invalid password"}))
elif data.get('type') == 'tunnel_request': elif data.get('type') == 'tunnel_request':
client_id = data['client_id'] client_id = data['client_id']
request_id = data['request_id'] request_id = data['request_id']
if client_id in clients: if client_id in clients and clients[client_id]['status'] == 'active':
# Store tunnel mapping # Store tunnel mapping
active_tunnels[request_id] = { active_tunnels[request_id] = {
'client_ws': clients[client_id], 'client_ws': clients[client_id]['websocket'],
'wsssh_ws': websocket, 'wsssh_ws': websocket,
'client_id': client_id 'client_id': client_id
} }
# Forward tunnel request to client # Forward tunnel request to client
await clients[client_id].send(json.dumps({ await clients[client_id]['websocket'].send(json.dumps({
"type": "tunnel_request", "type": "tunnel_request",
"request_id": request_id "request_id": request_id
})) }))
...@@ -412,7 +454,7 @@ async def handle_websocket(websocket, path=None): ...@@ -412,7 +454,7 @@ async def handle_websocket(websocket, path=None):
await websocket.send(json.dumps({ await websocket.send(json.dumps({
"type": "tunnel_error", "type": "tunnel_error",
"request_id": request_id, "request_id": request_id,
"error": "Client not registered" "error": "Client not registered or disconnected"
})) }))
elif data.get('type') == 'tunnel_data': elif data.get('type') == 'tunnel_data':
# Forward tunnel data using active tunnel mapping # Forward tunnel data using active tunnel mapping
...@@ -420,11 +462,14 @@ async def handle_websocket(websocket, path=None): ...@@ -420,11 +462,14 @@ async def handle_websocket(websocket, path=None):
if request_id in active_tunnels: if request_id in active_tunnels:
tunnel = active_tunnels[request_id] tunnel = active_tunnels[request_id]
# Forward to client # Forward to client
await tunnel['client_ws'].send(json.dumps({ if tunnel['client_id'] in clients and clients[tunnel['client_id']]['status'] == 'active':
"type": "tunnel_data", await tunnel['client_ws'].send(json.dumps({
"request_id": request_id, "type": "tunnel_data",
"data": data['data'] "request_id": request_id,
})) "data": data['data']
}))
else:
if debug: print(f"[DEBUG] [WebSocket] Cannot forward tunnel_data: client {tunnel['client_id']} not active")
elif data.get('type') == 'tunnel_response': elif data.get('type') == 'tunnel_response':
# Forward tunnel response from client to wsssh # Forward tunnel response from client to wsssh
request_id = data['request_id'] request_id = data['request_id']
...@@ -439,21 +484,24 @@ async def handle_websocket(websocket, path=None): ...@@ -439,21 +484,24 @@ async def handle_websocket(websocket, path=None):
request_id = data['request_id'] request_id = data['request_id']
if request_id in active_tunnels: if request_id in active_tunnels:
tunnel = active_tunnels[request_id] tunnel = active_tunnels[request_id]
# Forward close to client # Forward close to client if still active
await tunnel['client_ws'].send(json.dumps({ if tunnel['client_id'] in clients and clients[tunnel['client_id']]['status'] == 'active':
"type": "tunnel_close", await tunnel['client_ws'].send(json.dumps({
"request_id": request_id "type": "tunnel_close",
})) "request_id": request_id
}))
# Clean up tunnel # Clean up tunnel
del active_tunnels[request_id] del active_tunnels[request_id]
if debug: print(f"[DEBUG] [WebSocket] Tunnel {request_id} closed")
except websockets.exceptions.ConnectionClosed: except websockets.exceptions.ConnectionClosed:
# Remove from registry and clean up tunnels # Mark client as disconnected instead of removing immediately
disconnected_client = None disconnected_client = None
for cid, ws in clients.items(): for cid, client_info in clients.items():
if ws == websocket: if client_info['websocket'] == websocket:
disconnected_client = cid disconnected_client = cid
del clients[cid] clients[cid]['status'] = 'disconnected'
print(f"Client {cid} disconnected") clients[cid]['last_seen'] = time.time()
print(f"[DEBUG] [WebSocket] Client {cid} disconnected (marked for timeout)")
break break
# Clean up active tunnels for this client # Clean up active tunnels for this client
...@@ -464,6 +512,13 @@ async def handle_websocket(websocket, path=None): ...@@ -464,6 +512,13 @@ async def handle_websocket(websocket, path=None):
tunnels_to_remove.append(request_id) tunnels_to_remove.append(request_id)
for request_id in tunnels_to_remove: for request_id in tunnels_to_remove:
del active_tunnels[request_id] del active_tunnels[request_id]
if debug: print(f"[DEBUG] [WebSocket] Tunnel {request_id} cleaned up due to client disconnect")
async def cleanup_task():
"""Periodic task to clean up expired clients"""
while True:
await asyncio.sleep(10) # Run every 10 seconds
cleanup_expired_clients()
async def main(): async def main():
config = configparser.ConfigParser() config = configparser.ConfigParser()
...@@ -539,6 +594,9 @@ async def main(): ...@@ -539,6 +594,9 @@ async def main():
print(f"WebSocket SSH Daemon running on {args.host}:{args.port}") print(f"WebSocket SSH Daemon running on {args.host}:{args.port}")
# Start cleanup task
cleanup_coro = asyncio.create_task(cleanup_task())
# Start web interface if specified # Start web interface if specified
if args.web_host and args.web_port: if args.web_host and args.web_port:
# Handle HTTPS setup # Handle HTTPS setup
...@@ -565,7 +623,11 @@ async def main(): ...@@ -565,7 +623,11 @@ async def main():
flask_thread.start() flask_thread.start()
print(f"Web interface available at {protocol}://{args.web_host}:{args.web_port}") print(f"Web interface available at {protocol}://{args.web_host}:{args.web_port}")
# Wait for server to close
await ws_server.wait_closed() await ws_server.wait_closed()
# Cancel cleanup task
cleanup_coro.cancel()
if __name__ == '__main__': if __name__ == '__main__':
asyncio.run(main()) asyncio.run(main())
\ No newline at end of file
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <openssl/err.h> #include <openssl/err.h>
#include <getopt.h> #include <getopt.h>
#include <pthread.h> #include <pthread.h>
#include <errno.h>
#define BUFFER_SIZE 4096 #define BUFFER_SIZE 4096
#define DEFAULT_PORT 9898 #define DEFAULT_PORT 9898
...@@ -98,6 +99,7 @@ void load_config(wssshc_config_t *config) { ...@@ -98,6 +99,7 @@ void load_config(wssshc_config_t *config) {
tunnel_t *active_tunnel = NULL; tunnel_t *active_tunnel = NULL;
void *tunnel_thread(void *arg); void *tunnel_thread(void *arg);
void cleanup_tunnel();
void handle_tunnel_request(SSL *ssl, const char *request_id, int debug) { void handle_tunnel_request(SSL *ssl, const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex); pthread_mutex_lock(&tunnel_mutex);
...@@ -163,7 +165,7 @@ void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex, ...@@ -163,7 +165,7 @@ void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex,
int sock; int sock;
if (debug) { if (debug) {
printf("[DEBUG] Opening local SSH connection for tunnel %s\n", request_id); printf("[DEBUG] [TCP Tunnel] Opening local SSH connection for tunnel %s\n", request_id);
} }
// Create socket for local SSH connection // Create socket for local SSH connection
...@@ -195,12 +197,74 @@ void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex, ...@@ -195,12 +197,74 @@ void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex,
pthread_detach(thread); pthread_detach(thread);
if (debug) { if (debug) {
printf("[DEBUG] Local SSH connection established and tunnel thread started\n"); printf("[DEBUG] [TCP Tunnel] Local SSH connection established and tunnel thread started\n");
} }
} else { } else {
close(sock); close(sock);
} }
pthread_mutex_unlock(&tunnel_mutex); pthread_mutex_unlock(&tunnel_mutex);
} else {
// SSH connection already exists, verify it's still valid
char test_buf[1];
int result = recv(sock_fd, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
// SSH connection is broken, close it and create a new one
if (debug) {
printf("[DEBUG] [TCP Tunnel] Existing SSH connection is broken, creating new one\n");
}
close(sock_fd);
struct sockaddr_in addr;
int sock;
// Create new socket for local SSH connection
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Local socket creation failed");
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->sock = -1;
}
pthread_mutex_unlock(&tunnel_mutex);
return;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(22);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// Connect to local SSH
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Local SSH connection failed");
close(sock);
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->sock = -1;
}
pthread_mutex_unlock(&tunnel_mutex);
return;
}
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->sock = sock;
sock_fd = sock;
// Start tunnel thread
pthread_t thread;
pthread_create(&thread, NULL, tunnel_thread, ssl);
pthread_detach(thread);
if (debug) {
printf("[DEBUG] [TCP Tunnel] New SSH connection established and tunnel thread started\n");
}
} else {
close(sock);
}
pthread_mutex_unlock(&tunnel_mutex);
} else if (debug) {
printf("[DEBUG] [TCP Tunnel] Reusing existing SSH connection for tunnel %s\n", request_id);
}
} }
if (debug) { if (debug) {
...@@ -210,7 +274,10 @@ void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex, ...@@ -210,7 +274,10 @@ void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex,
// Decode hex data // Decode hex data
size_t data_len = strlen(data_hex) / 2; size_t data_len = strlen(data_hex) / 2;
char *data = malloc(data_len); char *data = malloc(data_len);
if (!data) return; if (!data) {
fprintf(stderr, "Memory allocation failed for tunnel data\n");
return;
}
for (size_t i = 0; i < data_len; i++) { for (size_t i = 0; i < data_len; i++) {
sscanf(data_hex + i * 2, "%2hhx", &data[i]); sscanf(data_hex + i * 2, "%2hhx", &data[i]);
...@@ -229,7 +296,12 @@ void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex, ...@@ -229,7 +296,12 @@ void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex,
} }
// Send to local SSH // Send to local SSH
send(sock_fd, data, data_len, 0); ssize_t sent = send(sock_fd, data, data_len, 0);
if (sent < 0) {
perror("Failed to send data to local SSH");
} else if ((size_t)sent != data_len) {
fprintf(stderr, "Partial send to local SSH: sent %zd of %zu bytes\n", sent, data_len);
}
free(data); free(data);
} }
...@@ -240,7 +312,7 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i ...@@ -240,7 +312,7 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i
free(active_tunnel); free(active_tunnel);
active_tunnel = NULL; active_tunnel = NULL;
if (debug) { if (debug) {
printf("[DEBUG] Tunnel %s closed\n", request_id); printf("[DEBUG] [TCP Tunnel] Tunnel %s closed\n", request_id);
} }
} }
pthread_mutex_unlock(&tunnel_mutex); pthread_mutex_unlock(&tunnel_mutex);
...@@ -264,11 +336,24 @@ void *tunnel_thread(void *arg) { ...@@ -264,11 +336,24 @@ void *tunnel_thread(void *arg) {
bytes_read = recv(sock, buffer, sizeof(buffer), 0); bytes_read = recv(sock, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) { if (bytes_read <= 0) {
if (bytes_read < 0) {
perror("Error reading from local SSH");
// Check if this is due to tunnel cleanup (ECONNRESET, EPIPE, etc.)
if (errno == ECONNRESET || errno == EPIPE || errno == EBADF) {
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] SSH connection closed due to tunnel reset\n");
}
}
} else {
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] Local SSH connection closed normally\n");
}
}
break; break;
} }
if (global_debug) { if (global_debug) {
printf("[DEBUG] Tunnel thread: read %d bytes from local SSH: ", bytes_read); printf("[DEBUG] [TCP Tunnel] Read %d bytes from local SSH: ", bytes_read);
for (int i = 0; i < bytes_read && i < 50; i++) { for (int i = 0; i < bytes_read && i < 50; i++) {
if (buffer[i] >= 32 && buffer[i] < 127) { if (buffer[i] >= 32 && buffer[i] < 127) {
printf("%c", buffer[i]); printf("%c", buffer[i]);
...@@ -282,7 +367,7 @@ void *tunnel_thread(void *arg) { ...@@ -282,7 +367,7 @@ void *tunnel_thread(void *arg) {
// Send as tunnel_response // Send as tunnel_response
char *hex_data = malloc(bytes_read * 2 + 1); char *hex_data = malloc(bytes_read * 2 + 1);
if (!hex_data) { if (!hex_data) {
perror("Memory allocation failed for hex_data"); fprintf(stderr, "Memory allocation failed for hex_data in tunnel_thread\n");
break; break;
} }
for (int i = 0; i < bytes_read; i++) { for (int i = 0; i < bytes_read; i++) {
...@@ -291,7 +376,7 @@ void *tunnel_thread(void *arg) { ...@@ -291,7 +376,7 @@ void *tunnel_thread(void *arg) {
hex_data[bytes_read * 2] = '\0'; hex_data[bytes_read * 2] = '\0';
if (global_debug) { if (global_debug) {
printf("[DEBUG] Sending tunnel_response, hex len: %zu, hex: %.100s...\n", strlen(hex_data), hex_data); printf("[DEBUG] [WebSocket] Sending tunnel_response, hex len: %zu, hex: %.100s...\n", strlen(hex_data), hex_data);
} }
char response[9000]; char response[9000];
...@@ -321,7 +406,7 @@ void *tunnel_thread(void *arg) { ...@@ -321,7 +406,7 @@ void *tunnel_thread(void *arg) {
char *frame = malloc(header_len + msg_len); char *frame = malloc(header_len + msg_len);
if (!frame) { if (!frame) {
perror("Memory allocation failed for frame"); fprintf(stderr, "Memory allocation failed for WebSocket frame in tunnel_thread\n");
free(hex_data); free(hex_data);
break; break;
} }
...@@ -360,7 +445,10 @@ void *tunnel_thread(void *arg) { ...@@ -360,7 +445,10 @@ void *tunnel_thread(void *arg) {
int frame_len = header_len + msg_len; int frame_len = header_len + msg_len;
if (!send_all(ssl, frame, frame_len)) { if (!send_all(ssl, frame, frame_len)) {
fprintf(stderr, "Send tunnel_response failed\n"); fprintf(stderr, "Send tunnel_response failed - tunnel may hang\n");
free(frame);
free(hex_data);
break;
} }
free(frame); free(frame);
...@@ -577,7 +665,7 @@ int send_json_message(SSL *ssl, const char *type, const char *id, const char *pa ...@@ -577,7 +665,7 @@ int send_json_message(SSL *ssl, const char *type, const char *id, const char *pa
return 1; return 1;
} }
void connect_to_server(const wssshc_config_t *config) { int connect_to_server(const wssshc_config_t *config) {
struct sockaddr_in server_addr; struct sockaddr_in server_addr;
struct hostent *he; struct hostent *he;
int sock; int sock;
...@@ -586,16 +674,19 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -586,16 +674,19 @@ void connect_to_server(const wssshc_config_t *config) {
char buffer[BUFFER_SIZE]; char buffer[BUFFER_SIZE];
int bytes_read; int bytes_read;
// Clean up any leftover tunnel state from previous connection
cleanup_tunnel();
// Resolve hostname // Resolve hostname
if ((he = gethostbyname(config->server_ip)) == NULL) { if ((he = gethostbyname(config->server_ip)) == NULL) {
herror("gethostbyname"); herror("gethostbyname");
return; return 1;
} }
// Create socket // Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed"); perror("Socket creation failed");
return; return 1;
} }
memset(&server_addr, 0, sizeof(server_addr)); memset(&server_addr, 0, sizeof(server_addr));
...@@ -607,14 +698,14 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -607,14 +698,14 @@ void connect_to_server(const wssshc_config_t *config) {
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed"); perror("Connection failed");
close(sock); close(sock);
return; return 1;
} }
// Initialize SSL // Initialize SSL
ssl_ctx = init_ssl_ctx(); ssl_ctx = init_ssl_ctx();
if (!ssl_ctx) { if (!ssl_ctx) {
close(sock); close(sock);
return; return 1;
} }
ssl = SSL_new(ssl_ctx); ssl = SSL_new(ssl_ctx);
...@@ -625,7 +716,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -625,7 +716,7 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return; return 1;
} }
// Perform WebSocket handshake // Perform WebSocket handshake
...@@ -633,7 +724,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -633,7 +724,7 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return; return 1;
} }
// Send registration message // Send registration message
...@@ -641,7 +732,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -641,7 +732,7 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return; return 1;
} }
// Read WebSocket frame with registration response // Read WebSocket frame with registration response
...@@ -651,7 +742,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -651,7 +742,7 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return; return 1;
} }
// Parse WebSocket frame with extended length support // Parse WebSocket frame with extended length support
...@@ -674,7 +765,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -674,7 +765,7 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return; return 1;
} }
payload_len = ((unsigned char)buffer[2] << 8) | (unsigned char)buffer[3]; payload_len = ((unsigned char)buffer[2] << 8) | (unsigned char)buffer[3];
header_len = 4; header_len = 4;
...@@ -684,7 +775,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -684,7 +775,7 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return; return 1;
} }
uint64_t len = 0; uint64_t len = 0;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
...@@ -715,7 +806,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -715,7 +806,7 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return; return 1;
} }
} else { } else {
buffer[bytes_read] = '\0'; buffer[bytes_read] = '\0';
...@@ -727,7 +818,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -727,7 +818,7 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return; return 2;
} }
printf("Connected and registered as %s\n", config->client_id); printf("Connected and registered as %s\n", config->client_id);
...@@ -738,14 +829,21 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -738,14 +829,21 @@ void connect_to_server(const wssshc_config_t *config) {
while (1) { while (1) {
bytes_read = SSL_read(ssl, buffer, sizeof(buffer)); bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) { if (bytes_read <= 0) {
if (config->debug) { if (bytes_read < 0) {
printf("[DEBUG] Connection lost\n"); ERR_print_errors_fp(stderr);
fprintf(stderr, "SSL read error\n");
} else {
if (config->debug) {
printf("[DEBUG] Connection closed by server\n");
}
} }
// Clean up tunnel resources before breaking
cleanup_tunnel();
break; break;
} }
if (config->debug) { if (config->debug) {
printf("[DEBUG] Read %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", bytes_read, buffer[0], buffer[1], buffer[2], buffer[3]); printf("[DEBUG] [WebSocket] Read %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", bytes_read, buffer[0], buffer[1], buffer[2], buffer[3]);
} }
// Parse WebSocket frame with extended length support // Parse WebSocket frame with extended length support
...@@ -753,16 +851,26 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -753,16 +851,26 @@ void connect_to_server(const wssshc_config_t *config) {
int masked = buffer[1] & 0x80; int masked = buffer[1] & 0x80;
int len_indicator = buffer[1] & 0x7F; int len_indicator = buffer[1] & 0x7F;
int header_len = 2; int header_len = 2;
size_t payload_len; size_t payload_len = 0;
if (len_indicator <= 125) { if (len_indicator <= 125) {
payload_len = len_indicator; payload_len = len_indicator;
} else if (len_indicator == 126) { } else if (len_indicator == 126) {
if (bytes_read < 4) continue; if (bytes_read < 4) {
if (config->debug) {
printf("[DEBUG] Incomplete extended length frame\n");
}
continue;
}
payload_len = ((unsigned char)buffer[2] << 8) | (unsigned char)buffer[3]; payload_len = ((unsigned char)buffer[2] << 8) | (unsigned char)buffer[3];
header_len = 4; header_len = 4;
} else if (len_indicator == 127) { } else if (len_indicator == 127) {
if (bytes_read < 10) continue; if (bytes_read < 10) {
if (config->debug) {
printf("[DEBUG] Incomplete extended length frame\n");
}
continue;
}
uint64_t len = 0; uint64_t len = 0;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
len = (len << 8) | (unsigned char)buffer[2 + i]; len = (len << 8) | (unsigned char)buffer[2 + i];
...@@ -788,7 +896,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -788,7 +896,7 @@ void connect_to_server(const wssshc_config_t *config) {
// Handle message // Handle message
if (config->debug) { if (config->debug) {
printf("[DEBUG] Received: %s\n", buffer); printf("[DEBUG] [WebSocket] Received message: %s\n", buffer);
} }
// Parse JSON message // Parse JSON message
...@@ -804,6 +912,9 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -804,6 +912,9 @@ void connect_to_server(const wssshc_config_t *config) {
char *close_quote = strchr(id_start, '"'); char *close_quote = strchr(id_start, '"');
if (close_quote) { if (close_quote) {
*close_quote = '\0'; *close_quote = '\0';
if (config->debug) {
printf("[DEBUG] [WebSocket] Received tunnel_request for ID: %s\n", id_start);
}
handle_tunnel_request(ssl, id_start, config->debug); handle_tunnel_request(ssl, id_start, config->debug);
} }
} }
...@@ -811,7 +922,7 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -811,7 +922,7 @@ void connect_to_server(const wssshc_config_t *config) {
} }
} else if (strstr(buffer, "tunnel_data")) { } else if (strstr(buffer, "tunnel_data")) {
if (config->debug) { if (config->debug) {
printf("[DEBUG] Received tunnel_data\n"); printf("[DEBUG] [WebSocket] Received tunnel_data message\n");
} }
// Extract request_id and data // Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\""); char *id_start = strstr(buffer, "\"request_id\"");
...@@ -852,6 +963,9 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -852,6 +963,9 @@ void connect_to_server(const wssshc_config_t *config) {
char *close_quote = strchr(id_start, '"'); char *close_quote = strchr(id_start, '"');
if (close_quote) { if (close_quote) {
*close_quote = '\0'; *close_quote = '\0';
if (config->debug) {
printf("[DEBUG] [WebSocket] Received tunnel_close for ID: %s\n", id_start);
}
handle_tunnel_close(ssl, id_start, config->debug); handle_tunnel_close(ssl, id_start, config->debug);
} }
} }
...@@ -859,18 +973,20 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -859,18 +973,20 @@ void connect_to_server(const wssshc_config_t *config) {
} }
} }
} else { } else {
if (config->debug) { fprintf(stderr, "WebSocket frame payload too large or incomplete: payload_len=%zu, buffer_size=%zu, bytes_read=%d\n",
printf("[DEBUG] Incomplete frame: need %zu bytes, got %d\n", (size_t)(payload_len + header_len), bytes_read); payload_len, sizeof(buffer) - header_len, bytes_read);
} break;
} }
} else if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) { // Close frame } else if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) { // Close frame
if (config->debug) { if (config->debug) {
printf("[DEBUG] Received close frame\n"); printf("[DEBUG] [WebSocket] Received close frame - cleaning up and reconnecting...\n");
} }
return; // Clean up tunnel resources before reconnecting
cleanup_tunnel();
return 0;
} else if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x89) { // Ping frame } else if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x89) { // Ping frame
if (config->debug) { if (config->debug) {
printf("[DEBUG] Received ping frame\n"); printf("[DEBUG] [WebSocket] Received ping frame\n");
} }
// Parse ping frame and send pong with echoed payload // Parse ping frame and send pong with echoed payload
int masked = buffer[1] & 0x80; int masked = buffer[1] & 0x80;
...@@ -881,11 +997,21 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -881,11 +997,21 @@ void connect_to_server(const wssshc_config_t *config) {
if (len_indicator <= 125) { if (len_indicator <= 125) {
payload_len = len_indicator; payload_len = len_indicator;
} else if (len_indicator == 126) { } else if (len_indicator == 126) {
if (bytes_read < 4) continue; if (bytes_read < 4) {
if (config->debug) {
printf("[DEBUG] Incomplete ping extended length frame\n");
}
continue;
}
payload_len = ((unsigned char)buffer[2] << 8) | (unsigned char)buffer[3]; payload_len = ((unsigned char)buffer[2] << 8) | (unsigned char)buffer[3];
header_len = 4; header_len = 4;
} else if (len_indicator == 127) { } else if (len_indicator == 127) {
if (bytes_read < 10) continue; if (bytes_read < 10) {
if (config->debug) {
printf("[DEBUG] Incomplete ping extended length frame\n");
}
continue;
}
uint64_t len = 0; uint64_t len = 0;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
len = (len << 8) | (unsigned char)buffer[2 + i]; len = (len << 8) | (unsigned char)buffer[2 + i];
...@@ -913,11 +1039,15 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -913,11 +1039,15 @@ void connect_to_server(const wssshc_config_t *config) {
// Send pong frame with same payload, properly masked // Send pong frame with same payload, properly masked
char pong_frame[14 + 1024]; // Max header + max pong payload char pong_frame[14 + 1024]; // Max header + max pong payload
if (payload_len > 1024) {
fprintf(stderr, "Pong payload too large: %zu bytes\n", payload_len);
continue;
}
pong_frame[0] = 0x8A; // Pong opcode pong_frame[0] = 0x8A; // Pong opcode
int pong_header_len = 2; int pong_header_len = 2;
char pong_mask_key[4]; char pong_mask_key[4];
size_t pong_payload_len = payload_len > 1024 ? 1024 : payload_len; size_t pong_payload_len = payload_len;
if (pong_payload_len <= 125) { if (pong_payload_len <= 125) {
pong_frame[1] = 0x80 | (int)pong_payload_len; // MASK + length pong_frame[1] = 0x80 | (int)pong_payload_len; // MASK + length
...@@ -953,10 +1083,10 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -953,10 +1083,10 @@ void connect_to_server(const wssshc_config_t *config) {
int pong_frame_len = pong_header_len + (int)pong_payload_len; int pong_frame_len = pong_header_len + (int)pong_payload_len;
if (config->debug) { if (config->debug) {
printf("[DEBUG] Sending pong frame, len: %d\n", pong_frame_len); printf("[DEBUG] [WebSocket] Sending pong frame, len: %d\n", pong_frame_len);
} }
if (!send_all(ssl, pong_frame, pong_frame_len)) { if (!send_all(ssl, pong_frame, pong_frame_len)) {
fprintf(stderr, "Send pong failed\n"); fprintf(stderr, "[ERROR] [WebSocket] Send pong failed\n");
} }
} }
} }
...@@ -966,6 +1096,35 @@ void connect_to_server(const wssshc_config_t *config) { ...@@ -966,6 +1096,35 @@ void connect_to_server(const wssshc_config_t *config) {
SSL_free(ssl); SSL_free(ssl);
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);
close(sock); close(sock);
return 1;
}
void cleanup_tunnel() {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel) {
if (active_tunnel->sock >= 0) {
// Check if SSH connection is still valid before closing
char test_buf[1];
int result = recv(active_tunnel->sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE))) {
// SSH connection is closed or broken, safe to close
close(active_tunnel->sock);
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] Closed broken SSH connection\n");
}
} else {
// SSH connection appears valid, don't close it
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] Keeping SSH connection alive for potential reuse\n");
}
// Reset socket to -1 so it will be reconnected if needed
active_tunnel->sock = -1;
}
}
free(active_tunnel);
active_tunnel = NULL;
}
pthread_mutex_unlock(&tunnel_mutex);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
...@@ -1002,9 +1161,19 @@ int main(int argc, char *argv[]) { ...@@ -1002,9 +1161,19 @@ int main(int argc, char *argv[]) {
printf("WebSocket SSH Client starting...\n"); printf("WebSocket SSH Client starting...\n");
while (1) { while (1) {
connect_to_server(&config); int result = connect_to_server(&config);
printf("Connection lost, retrying in %d seconds...\n", config.interval); if (result == 1) {
sleep(config.interval); // Error condition - use normal retry interval
printf("Connection lost, retrying in %d seconds...\n", config.interval);
sleep(config.interval);
} else if (result == 0) {
// Close frame received - add small delay to prevent rapid reconnection
if (config.debug) {
printf("[DEBUG] Server initiated disconnect, reconnecting in 2 seconds...\n");
}
sleep(2);
}
// result == 2 (registration failed) also uses normal interval
} }
// Cleanup // Cleanup
......
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