Implement daemon mode for lazy tunnel initialization

- Added run_daemon_mode() function for lazy tunnel establishment
- Daemon mode waits for first connection attempt before establishing tunnel
- Reduces resource usage by not connecting until actually needed
- Maintains all existing functionality after tunnel is established
- Fixed compilation errors and ensured clean build
- Updated mode handling to support daemon mode
parent bd114443
No preview for this file type
...@@ -310,6 +310,614 @@ int run_script_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -310,6 +310,614 @@ int run_script_mode(wsssh_config_t *config, const char *client_id, const char *w
return 1; return 1;
} }
int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) {
// Daemon mode: Lazy tunnel establishment
if (config->debug) {
printf("[DEBUG] Starting daemon mode with lazy tunnel establishment\n");
fflush(stdout);
}
// Find available local port
int local_port = config->local_port ? atoi(config->local_port) : find_available_port();
if (local_port == 0) {
fprintf(stderr, "Error: Could not find available local port\n");
return 1;
}
if (config->debug) {
printf("[DEBUG] Using local port: %d\n", local_port);
fflush(stdout);
}
// Create listening socket
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("Local socket creation failed");
return 1;
}
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(local_port);
// Use specified tunnel_host or default to 127.0.0.1
if (config->tunnel_host && strcmp(config->tunnel_host, "127.0.0.1") != 0) {
struct hostent *tunnel_he;
if ((tunnel_he = gethostbyname(config->tunnel_host)) == NULL) {
if (config->debug) {
fprintf(stderr, "[DEBUG] Failed to resolve tunnel_host '%s', using 127.0.0.1\n", config->tunnel_host);
}
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
} else {
local_addr.sin_addr = *((struct in_addr *)tunnel_he->h_addr);
if (config->debug) {
printf("[DEBUG] Binding tunnel to %s:%d\n", config->tunnel_host, local_port);
fflush(stdout);
}
}
} else {
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (config->debug) {
printf("[DEBUG] Binding tunnel to 127.0.0.1:%d\n", local_port);
fflush(stdout);
}
}
if (bind(listen_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("Local bind failed");
close(listen_sock);
return 1;
}
if (listen(listen_sock, 1) < 0) {
perror("Local listen failed");
close(listen_sock);
return 1;
}
if (config->mode != MODE_SILENT) {
printf("Daemon mode: Listening on port %d, waiting for connection...\n", local_port);
printf("Tunnel will be established on first connection attempt.\n");
fflush(stdout);
}
// Wait for first connection
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int accepted_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len);
if (accepted_sock < 0) {
perror("Local accept failed");
close(listen_sock);
return 1;
}
close(listen_sock); // No longer needed
if (config->debug) {
printf("[DEBUG] Connection received, establishing tunnel...\n");
fflush(stdout);
}
// Now establish the tunnel
int tunnel_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config->debug, 0, config->tunnel_host);
if (tunnel_sock < 0) {
fprintf(stderr, "Failed to establish tunnel in daemon mode\n");
close(accepted_sock);
return 1;
}
// Print tunnel information (unless silent mode)
if (config->mode != MODE_SILENT) {
printf("\n");
printf("========================================\n");
printf(" WEBSSH TUNNEL READY\n");
printf("========================================\n");
printf("Tunnel established successfully!\n");
printf("Local port: %d\n", local_port);
printf("Target: %s@%s\n", client_id, wssshd_host);
printf("\n");
printf("Connect manually using one of these commands:\n");
printf("\n");
printf(" Telnet:\n");
printf(" telnet localhost %d\n", local_port);
printf("\n");
printf(" Netcat:\n");
printf(" nc localhost %d\n", local_port);
printf("\n");
printf(" SSH (if connecting to SSH server):\n");
printf(" ssh -p %d user@localhost\n", local_port);
printf("\n");
printf(" SCP (if connecting to SSH server):\n");
printf(" scp -P %d user@localhost:/remote/path ./local/path\n", local_port);
printf("\n");
printf(" Any TCP client:\n");
printf(" Connect to localhost:%d\n", local_port);
printf("\n");
printf("Press Ctrl+C to close the tunnel and exit.\n");
printf("========================================\n");
printf("\n");
}
// Continue with normal tunnel operation
// Set the accepted socket with mutex protection
pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = accepted_sock;
// Send any buffered data to the client immediately
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
if (config->debug) {
printf("[DEBUG - Tunnel] Sending %zu bytes of buffered server response to client\n", active_tunnel->incoming_buffer->used);
fflush(stdout);
}
ssize_t sent = send(accepted_sock, active_tunnel->incoming_buffer->buffer, active_tunnel->incoming_buffer->used, 0);
if (sent > 0) {
frame_buffer_consume(active_tunnel->incoming_buffer, sent);
if (config->debug) {
printf("[DEBUG] Sent %zd bytes of buffered server response to client\n", sent);
fflush(stdout);
}
}
}
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Local connection accepted! Starting data forwarding...\n");
fflush(stdout);
}
// Get initial SSL connection for thread
pthread_mutex_lock(&tunnel_mutex);
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
pthread_mutex_unlock(&tunnel_mutex);
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (!thread_args) {
perror("Memory allocation failed for thread args");
close(active_tunnel->local_sock);
free(active_tunnel);
active_tunnel = NULL;
pthread_mutex_destroy(&tunnel_mutex);
return 1;
}
thread_args->ssl = current_ssl;
thread_args->tunnel = active_tunnel;
thread_args->debug = config->debug;
pthread_t thread;
pthread_create(&thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(thread);
// Main tunnel loop - handle WebSocket messages
char buffer[BUFFER_SIZE];
int bytes_read;
fd_set readfds;
struct timeval tv;
int tunnel_broken = 0;
// Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0;
while (1) {
// Get SSL fd with mutex protection
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
if (active_tunnel && active_tunnel->broken) {
tunnel_broken = 1;
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
} else {
// normal closure
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel is no longer active, exiting main loop\n");
fflush(stdout);
}
break;
}
}
int ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
// Check if local socket is still valid
if (active_tunnel->local_sock < 0) {
if (config->debug) {
printf("[DEBUG - Tunnel] Local socket is invalid, tunnel broken\n");
fflush(stdout);
}
tunnel_broken = 1;
active_tunnel->broken = 1;
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to invalid local socket...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
}
// Check if the local socket connection is broken
char test_buf[1];
int result = recv(active_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE || errno == EBADF))) {
if (config->debug) {
printf("[DEBUG - Tunnel] Local socket connection is broken (errno=%d), sending tunnel_close\n", errno);
fflush(stdout);
}
tunnel_broken = 1;
active_tunnel->broken = 1;
// Send tunnel_close notification
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on SSL socket with timeout
FD_ZERO(&readfds);
FD_SET(ssl_fd, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 50000; // 50ms timeout
int retval = select(ssl_fd + 1, &readfds, NULL, NULL, &tv);
if (retval == -1) {
if (config->debug) {
perror("[DEBUG - WebSockets] select on SSL fd failed");
fflush(stdout);
}
tunnel_broken = 1;
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to select failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel became inactive during timeout, exiting\n");
fflush(stdout);
}
tunnel_broken = 1;
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
continue;
}
// Read more data if we don't have a complete frame
if (FD_ISSET(ssl_fd, &readfds)) {
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
// Validate SSL connection state
if (SSL_get_shutdown(current_ssl) & SSL_RECEIVED_SHUTDOWN) {
if (config->debug) {
printf("[DEBUG - WebSockets] SSL connection has received shutdown\n");
fflush(stdout);
}
cleanup_tunnel(config->debug);
break;
}
// Set up timeout for SSL read
fd_set readfds_timeout;
struct timeval tv_timeout;
int sock_fd = SSL_get_fd(current_ssl);
FD_ZERO(&readfds_timeout);
FD_SET(sock_fd, &readfds_timeout);
tv_timeout.tv_sec = 5;
tv_timeout.tv_usec = 0;
int select_result = select(sock_fd + 1, &readfds_timeout, NULL, NULL, &tv_timeout);
if (select_result == -1) {
if (config->debug) {
perror("[DEBUG - WebSockets] select failed");
fflush(stdout);
}
cleanup_tunnel(config->debug);
break;
} else if (select_result == 0) {
if (config->debug) {
printf("[DEBUG - WebSockets] SSL read timeout\n");
fflush(stdout);
}
continue;
}
bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read <= 0) {
if (bytes_read < 0) {
int ssl_error = SSL_get_error(current_ssl, bytes_read);
if (config->debug) {
printf("[DEBUG - WebSockets] SSL read error: %d\n", ssl_error);
fflush(stdout);
}
// Handle transient SSL errors
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
if (config->debug) {
printf("[DEBUG - WebSockets] Transient SSL error, retrying...\n");
fflush(stdout);
}
usleep(10000);
continue;
}
// Print SSL error details
char error_buf[256];
ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf));
if (config->debug) {
printf("[DEBUG - WebSockets] SSL error details: %s\n", error_buf);
fflush(stdout);
}
fprintf(stderr, "SSL read error (%d): %s\n", ssl_error, error_buf);
} else {
if (config->debug) {
printf("[DEBUG - WebSockets] Connection closed by server (EOF)\n");
fflush(stdout);
}
}
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket connection lost, attempting reconnection...\n");
fflush(stdout);
}
// Attempt reconnection
int reconnect_attempts = 0;
int max_reconnect_attempts = 3;
int reconnected = 0;
while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout);
}
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
if (reconnect_websocket(active_tunnel, wssshd_host, wssshd_port, client_id, active_tunnel->request_id, config->debug) == 0) {
reconnected = 1;
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection successful, continuing tunnel\n");
fflush(stdout);
}
// Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
}
pthread_mutex_unlock(&tunnel_mutex);
if (!reconnected) {
reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) {
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection failed, waiting 1 second...\n");
fflush(stdout);
}
sleep(1);
}
}
}
if (!reconnected) {
if (config->debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n");
fflush(stdout);
}
tunnel_broken = 1;
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to connection failure...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
}
// Skip processing this iteration since we just reconnected
continue;
}
frame_buffer_used += bytes_read;
if (config->debug) {
printf("[DEBUG - WebSockets] Accumulated %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", frame_buffer_used, frame_buffer[0], frame_buffer[1], frame_buffer[2], frame_buffer[3]);
fflush(stdout);
}
}
// Try to parse WebSocket frame
char *payload;
int payload_len;
if (parse_websocket_frame(frame_buffer, frame_buffer_used, &payload, &payload_len)) {
// Frame is complete, determine frame type
unsigned char frame_type = frame_buffer[0] & 0x8F;
if (frame_type == 0x88) { // Close frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received close frame from server\n");
fflush(stdout);
}
tunnel_broken = 1;
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to server close frame...\n");
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
goto cleanup_and_exit;
} else if (frame_type == 0x89) { // Ping frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received ping frame, sending pong\n");
fflush(stdout);
}
// Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) {
if (config->debug) {
printf("[DEBUG - WebSockets] Failed to send pong frame\n");
fflush(stdout);
}
}
} else if (frame_type == 0x8A) { // Pong frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received pong frame\n");
fflush(stdout);
}
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// Copy payload to buffer
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
} else {
fprintf(stderr, "Payload too large for processing buffer\n");
frame_buffer_used = 0;
continue;
}
// Handle message
if (config->debug) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
fflush(stdout);
}
// Handle tunnel messages
if (strstr(buffer, "tunnel_data") || strstr(buffer, "tunnel_response")) {
if (config->debug) {
if (strstr(buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] Received tunnel_data message\n");
} else {
printf("[DEBUG - Tunnel] Received tunnel_response message\n");
}
fflush(stdout);
}
// Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
char *data_colon = strchr(data_start, ':');
if (data_colon) {
char *data_quote = strchr(data_colon, '"');
if (data_quote) {
data_start = data_quote + 1;
char *data_end = strchr(data_start, '"');
if (data_end) {
*data_end = '\0';
handle_tunnel_data(current_ssl, id_start, data_start, config->debug);
}
}
}
}
}
}
}
} else if (strstr(buffer, "tunnel_close")) {
if (config->debug) {
printf("[DEBUG - Tunnel] Received tunnel_close message\n");
fflush(stdout);
}
char *id_start = strstr(buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
handle_tunnel_close(current_ssl, id_start, config->debug);
}
}
}
}
} else {
if (config->debug) {
printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer);
fflush(stdout);
}
}
}
// Remove processed frame from buffer
int frame_size = (payload - frame_buffer) + payload_len;
if (frame_size < frame_buffer_used) {
memmove(frame_buffer, frame_buffer + frame_size, frame_buffer_used - frame_size);
frame_buffer_used -= frame_size;
} else {
frame_buffer_used = 0;
}
} else {
// Frame not complete yet, continue reading
continue;
}
}
}
cleanup_and_exit:
// Cleanup section
if (config->debug) {
printf("[DEBUG - Tunnel] Performing cleanup and exiting\n");
fflush(stdout);
}
// Cleanup
if (active_tunnel) {
if (active_tunnel->local_sock >= 0) {
close(active_tunnel->local_sock);
}
if (active_tunnel->ssl) {
SSL_free(active_tunnel->ssl);
}
free(active_tunnel);
active_tunnel = NULL;
}
free(config->local_port);
free(config->tunnel);
free(config->tunnel_control);
free(config->service);
free(config->local_port);
free(config->tunnel);
free(config->tunnel_control);
free(config->service);
// Note: config_domain, config_clientid, etc. are freed in main()
pthread_mutex_destroy(&tunnel_mutex);
pthread_mutex_destroy(&ssl_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Cleanup complete, exiting with code %d\n", tunnel_broken ? 1 : 0);
fflush(stdout);
}
return tunnel_broken ? 1 : 0;
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Read config from wsssht.conf // Read config from wsssht.conf
char *config_domain = read_config_value_from_file("wssshd-host", "wsssht"); char *config_domain = read_config_value_from_file("wssshd-host", "wsssht");
...@@ -474,6 +1082,11 @@ int main(int argc, char *argv[]) { ...@@ -474,6 +1082,11 @@ int main(int argc, char *argv[]) {
} }
// MODE_INTERACTIVE and MODE_SILENT continue with normal flow // MODE_INTERACTIVE and MODE_SILENT continue with normal flow
// Handle daemon mode - lazy tunnel establishment
if (config.daemon) {
return run_daemon_mode(&config, client_id, wssshd_host, wssshd_port);
}
// Find available local port // Find available local port
int local_port = config.local_port ? atoi(config.local_port) : find_available_port(); int local_port = config.local_port ? atoi(config.local_port) : find_available_port();
if (local_port == 0) { if (local_port == 0) {
......
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