Test peus concluants
This commit is contained in:
21
docker-compose.yaml
Normal file
21
docker-compose.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
services:
|
||||||
|
rdp-broker:
|
||||||
|
image: easylinux/rdp-broker:1.0-3
|
||||||
|
container_name: rdp-broker
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- ./rdp-broker.env
|
||||||
|
volumes:
|
||||||
|
- ./targets.yaml:/etc/rdpbroker/targets.yaml:ro
|
||||||
|
ports:
|
||||||
|
- "3389:3389"
|
||||||
|
rdp-web-gateway:
|
||||||
|
image: easylinux/web-gateway:1.4
|
||||||
|
container_name: web-gateway
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- ./rdp-web-gateway.env
|
||||||
|
ports:
|
||||||
|
- "8085:8080"
|
||||||
|
depends_on:
|
||||||
|
- rdp-broker
|
||||||
19
rdp-broker.env
Normal file
19
rdp-broker.env
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# RdpBroker Configuration
|
||||||
|
|
||||||
|
# Samba Active Directory Server
|
||||||
|
SAMBA_AD_SERVER=192.168.100.240
|
||||||
|
SAMBA_AD_BASE_DN=DC=aipice,DC=local
|
||||||
|
SAMBA_PORT=389
|
||||||
|
|
||||||
|
# LDAP Base DN for user authentication
|
||||||
|
# Example: DC=easylinux,DC=lan
|
||||||
|
BASE_DN=DC=aipice,DC=local
|
||||||
|
|
||||||
|
# RDP Listen Port
|
||||||
|
RDP_LISTEN_PORT=3389
|
||||||
|
|
||||||
|
# Targets configuration file
|
||||||
|
TARGETS_CONFIG=/etc/rdpbroker/targets.yaml
|
||||||
|
|
||||||
|
# Log Level: 0=ERROR, 1=WARN, 2=INFO, 3=DEBUG
|
||||||
|
LOG_LEVEL=2
|
||||||
13
rdp-web-gateway.env
Normal file
13
rdp-web-gateway.env
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Web Gateway Configuration
|
||||||
|
|
||||||
|
# Server Port
|
||||||
|
PORT=8080
|
||||||
|
NODE_ENV=development
|
||||||
|
|
||||||
|
# RdpBroker Connection
|
||||||
|
RDP_BROKER_HOST=rdp-broker
|
||||||
|
RDP_BROKER_PORT=3389
|
||||||
|
|
||||||
|
# Optional: Pre-configure RDP Targets (JSON array)
|
||||||
|
# Leave empty to get targets dynamically from RdpBroker
|
||||||
|
# RDP_TARGETS=[{"name":"Server1","host":"192.168.1.100","port":3389,"description":"Test Server"}]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
FROM alpine:3.18 AS builder
|
FROM alpine:3.22 AS builder
|
||||||
|
|
||||||
# Install build dependencies
|
# Install build dependencies
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
@@ -19,8 +19,7 @@ COPY *.c *.h Makefile ./
|
|||||||
RUN make deps-alpine && make
|
RUN make deps-alpine && make
|
||||||
|
|
||||||
# Runtime stage
|
# Runtime stage
|
||||||
FROM alpine:3.18
|
FROM alpine:3.22
|
||||||
|
|
||||||
# Install runtime dependencies
|
# Install runtime dependencies
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
libldap \
|
libldap \
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include "rdp_broker.h"
|
#include "rdp_broker.h"
|
||||||
|
|
||||||
int global_log_level = LOG_INFO;
|
int global_log_level = LOG_INFO;
|
||||||
static volatile bool running = true;
|
volatile bool running = true; /* Global running flag */
|
||||||
|
|
||||||
void signal_handler(int signum) {
|
void signal_handler(int signum) {
|
||||||
if (signum == SIGINT || signum == SIGTERM) {
|
if (signum == SIGINT || signum == SIGTERM) {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
/* Global running flag for graceful shutdown */
|
||||||
|
extern volatile bool running;
|
||||||
|
|
||||||
#define MAX_TARGETS 100
|
#define MAX_TARGETS 100
|
||||||
#define MAX_SESSIONS 1000
|
#define MAX_SESSIONS 1000
|
||||||
#define MAX_USERNAME_LEN 256
|
#define MAX_USERNAME_LEN 256
|
||||||
@@ -69,11 +72,16 @@ int ldap_bind_check(const char *server, int port, const char *username,
|
|||||||
int start_rdp_server(broker_config_t *config, session_manager_t *sm);
|
int start_rdp_server(broker_config_t *config, session_manager_t *sm);
|
||||||
int handle_rdp_connection(int client_fd, broker_config_t *config,
|
int handle_rdp_connection(int client_fd, broker_config_t *config,
|
||||||
session_manager_t *sm);
|
session_manager_t *sm);
|
||||||
|
int handle_auth_protocol(int client_fd, char *initial_buffer, int initial_len,
|
||||||
|
broker_config_t *config, session_manager_t *sm,
|
||||||
|
const char *client_ip);
|
||||||
|
void send_json_error(int fd, const char *message);
|
||||||
|
int send_targets_json(int fd, broker_config_t *config, const char *username);
|
||||||
int present_login_screen(int client_fd, char *username, char *password);
|
int present_login_screen(int client_fd, char *username, char *password);
|
||||||
int present_target_menu(int client_fd, broker_config_t *config,
|
int present_target_menu(int client_fd, broker_config_t *config,
|
||||||
rdp_target_t **selected_target);
|
rdp_target_t **selected_target);
|
||||||
int forward_rdp_connection(int client_fd, rdp_target_t *target,
|
int forward_rdp_connection(int client_fd, const char *target_host,
|
||||||
session_info_t *session);
|
int target_port, session_info_t *session);
|
||||||
|
|
||||||
/* session_manager.c */
|
/* session_manager.c */
|
||||||
int init_session_manager(session_manager_t *sm);
|
int init_session_manager(session_manager_t *sm);
|
||||||
|
|||||||
302
src/rdp_server.c
302
src/rdp_server.c
@@ -3,6 +3,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/select.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
@@ -24,6 +25,8 @@ int start_rdp_server(broker_config_t *config, session_manager_t *sm) {
|
|||||||
struct sockaddr_in client_addr;
|
struct sockaddr_in client_addr;
|
||||||
socklen_t client_len = sizeof(client_addr);
|
socklen_t client_len = sizeof(client_addr);
|
||||||
pthread_t thread_id;
|
pthread_t thread_id;
|
||||||
|
struct timeval timeout;
|
||||||
|
fd_set read_fds;
|
||||||
|
|
||||||
/* Create listening socket */
|
/* Create listening socket */
|
||||||
server_fd = create_listening_socket(config->rdp_listen_port);
|
server_fd = create_listening_socket(config->rdp_listen_port);
|
||||||
@@ -35,9 +38,30 @@ int start_rdp_server(broker_config_t *config, session_manager_t *sm) {
|
|||||||
LOG(LOG_INFO, "RDP server listening on port %d", config->rdp_listen_port);
|
LOG(LOG_INFO, "RDP server listening on port %d", config->rdp_listen_port);
|
||||||
|
|
||||||
/* Accept connections in a loop */
|
/* Accept connections in a loop */
|
||||||
while (1) {
|
while (running) {
|
||||||
|
/* Use select with timeout to check running flag periodically */
|
||||||
|
FD_ZERO(&read_fds);
|
||||||
|
FD_SET(server_fd, &read_fds);
|
||||||
|
timeout.tv_sec = 1; /* 1 second timeout */
|
||||||
|
timeout.tv_usec = 0;
|
||||||
|
|
||||||
|
int ready = select(server_fd + 1, &read_fds, NULL, NULL, &timeout);
|
||||||
|
|
||||||
|
if (ready < 0) {
|
||||||
|
if (!running) break; /* Shutting down */
|
||||||
|
LOG(LOG_ERROR, "Select failed: %s", strerror(errno));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ready == 0) {
|
||||||
|
/* Timeout - check running flag and continue */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Socket is ready for accept */
|
||||||
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
|
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
|
||||||
if (client_fd < 0) {
|
if (client_fd < 0) {
|
||||||
|
if (!running) break; /* Shutting down */
|
||||||
LOG(LOG_ERROR, "Accept failed: %s", strerror(errno));
|
LOG(LOG_ERROR, "Accept failed: %s", strerror(errno));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -70,6 +94,8 @@ int start_rdp_server(broker_config_t *config, session_manager_t *sm) {
|
|||||||
pthread_detach(thread_id);
|
pthread_detach(thread_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Clean shutdown */
|
||||||
|
LOG(LOG_INFO, "Closing server socket...");
|
||||||
close(server_fd);
|
close(server_fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -133,8 +159,163 @@ int create_listening_socket(int port) {
|
|||||||
return server_fd;
|
return server_fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Handle AUTH/SELECT protocol from web-gateway */
|
||||||
|
int handle_auth_protocol(int client_fd, char *initial_buffer, int initial_len,
|
||||||
|
broker_config_t *config, session_manager_t *sm,
|
||||||
|
const char *client_ip) {
|
||||||
|
char username[MAX_USERNAME_LEN] = {0};
|
||||||
|
char password[MAX_USERNAME_LEN] = {0};
|
||||||
|
char target_name[MAX_HOSTNAME_LEN] = {0};
|
||||||
|
char buffer[4096];
|
||||||
|
rdp_target_t *selected_target = NULL;
|
||||||
|
session_info_t *session = NULL;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Parse AUTH\nusername\npassword\n from initial_buffer */
|
||||||
|
char *line = initial_buffer;
|
||||||
|
char *lines[3];
|
||||||
|
int line_count = 0;
|
||||||
|
|
||||||
|
char *token = strtok(line, "\n");
|
||||||
|
while (token != NULL && line_count < 3) {
|
||||||
|
lines[line_count++] = token;
|
||||||
|
token = strtok(NULL, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line_count < 3 || strcmp(lines[0], "AUTH") != 0) {
|
||||||
|
LOG(LOG_ERROR, "Invalid AUTH protocol format");
|
||||||
|
send_json_error(client_fd, "Invalid protocol format");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(username, lines[1], MAX_USERNAME_LEN - 1);
|
||||||
|
strncpy(password, lines[2], MAX_USERNAME_LEN - 1);
|
||||||
|
|
||||||
|
/* Authenticate user */
|
||||||
|
LOG(LOG_INFO, "Authenticating user: %s from %s", username, client_ip);
|
||||||
|
if (authenticate_user(username, password, config->samba_server,
|
||||||
|
config->samba_port, config->base_dn) != 0) {
|
||||||
|
LOG(LOG_WARN, "Authentication failed for user: %s", username);
|
||||||
|
send_json_error(client_fd, "Invalid credentials");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(LOG_INFO, "User %s authenticated successfully", username);
|
||||||
|
|
||||||
|
/* Send targets list as JSON */
|
||||||
|
if (send_targets_json(client_fd, config, username) != 0) {
|
||||||
|
LOG(LOG_ERROR, "Failed to send targets list");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for SELECT message */
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
ret = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
|
||||||
|
if (ret <= 0) {
|
||||||
|
LOG(LOG_ERROR, "Failed to receive SELECT message");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse SELECT\ntarget_name\n */
|
||||||
|
line_count = 0;
|
||||||
|
line = buffer;
|
||||||
|
token = strtok(line, "\n");
|
||||||
|
while (token != NULL && line_count < 2) {
|
||||||
|
lines[line_count++] = token;
|
||||||
|
token = strtok(NULL, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line_count < 2 || strcmp(lines[0], "SELECT") != 0) {
|
||||||
|
LOG(LOG_ERROR, "Invalid SELECT protocol format");
|
||||||
|
send_json_error(client_fd, "Invalid protocol format");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(target_name, lines[1], MAX_HOSTNAME_LEN - 1);
|
||||||
|
|
||||||
|
/* Find target by name */
|
||||||
|
for (int i = 0; i < config->target_count; i++) {
|
||||||
|
if (strcmp(config->targets[i].name, target_name) == 0) {
|
||||||
|
selected_target = &config->targets[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selected_target) {
|
||||||
|
LOG(LOG_ERROR, "Target not found: %s", target_name);
|
||||||
|
send_json_error(client_fd, "Target not found");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(LOG_INFO, "User %s selected target: %s (%s:%d)",
|
||||||
|
username, selected_target->name, selected_target->host, selected_target->port);
|
||||||
|
|
||||||
|
/* Send RDP ready message */
|
||||||
|
const char *ready_msg = "{\"type\":\"rdp_ready\"}\n\n";
|
||||||
|
send(client_fd, ready_msg, strlen(ready_msg), 0);
|
||||||
|
|
||||||
|
/* Create session */
|
||||||
|
session = create_session(sm, username, client_ip, selected_target, client_fd);
|
||||||
|
if (!session) {
|
||||||
|
LOG(LOG_ERROR, "Failed to create session");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect to target RDP server and forward traffic */
|
||||||
|
LOG(LOG_INFO, "Connecting to target RDP server %s:%d",
|
||||||
|
selected_target->host, selected_target->port);
|
||||||
|
|
||||||
|
ret = forward_rdp_connection(client_fd, selected_target->host,
|
||||||
|
selected_target->port, session);
|
||||||
|
|
||||||
|
/* Cleanup session */
|
||||||
|
close_session(sm, session->session_id);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send JSON error message */
|
||||||
|
void send_json_error(int fd, const char *message) {
|
||||||
|
char buffer[512];
|
||||||
|
snprintf(buffer, sizeof(buffer),
|
||||||
|
"{\"type\":\"auth_failed\",\"message\":\"%s\"}\n\n", message);
|
||||||
|
send(fd, buffer, strlen(buffer), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send targets list as JSON */
|
||||||
|
int send_targets_json(int fd, broker_config_t *config, const char *username) {
|
||||||
|
char buffer[8192];
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
/* Start JSON */
|
||||||
|
offset += snprintf(buffer + offset, sizeof(buffer) - offset,
|
||||||
|
"{\"type\":\"auth_success\",\"targets\":[");
|
||||||
|
|
||||||
|
/* Add targets - for now, all targets (TODO: filter by user groups) */
|
||||||
|
for (int i = 0; i < config->target_count; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
offset += snprintf(buffer + offset, sizeof(buffer) - offset, ",");
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += snprintf(buffer + offset, sizeof(buffer) - offset,
|
||||||
|
"{\"name\":\"%s\",\"host\":\"%s\",\"port\":%d,\"description\":\"%s\"}",
|
||||||
|
config->targets[i].name,
|
||||||
|
config->targets[i].host,
|
||||||
|
config->targets[i].port,
|
||||||
|
config->targets[i].description);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End JSON */
|
||||||
|
offset += snprintf(buffer + offset, sizeof(buffer) - offset, "]}\n\n");
|
||||||
|
|
||||||
|
LOG(LOG_DEBUG, "Sending %d targets to user %s", config->target_count, username);
|
||||||
|
|
||||||
|
return send(fd, buffer, strlen(buffer), 0) > 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
int handle_rdp_connection(int client_fd, broker_config_t *config,
|
int handle_rdp_connection(int client_fd, broker_config_t *config,
|
||||||
session_manager_t *sm) {
|
session_manager_t *sm) {
|
||||||
|
char buffer[4096];
|
||||||
char username[MAX_USERNAME_LEN] = {0};
|
char username[MAX_USERNAME_LEN] = {0};
|
||||||
char password[MAX_USERNAME_LEN] = {0};
|
char password[MAX_USERNAME_LEN] = {0};
|
||||||
rdp_target_t *selected_target = NULL;
|
rdp_target_t *selected_target = NULL;
|
||||||
@@ -142,69 +323,32 @@ int handle_rdp_connection(int client_fd, broker_config_t *config,
|
|||||||
struct sockaddr_in addr;
|
struct sockaddr_in addr;
|
||||||
socklen_t addr_len = sizeof(addr);
|
socklen_t addr_len = sizeof(addr);
|
||||||
char client_ip[INET_ADDRSTRLEN] = {0};
|
char client_ip[INET_ADDRSTRLEN] = {0};
|
||||||
|
int ret;
|
||||||
|
|
||||||
/* Get client IP */
|
/* Get client IP */
|
||||||
if (getpeername(client_fd, (struct sockaddr *)&addr, &addr_len) == 0) {
|
if (getpeername(client_fd, (struct sockaddr *)&addr, &addr_len) == 0) {
|
||||||
inet_ntop(AF_INET, &addr.sin_addr, client_ip, INET_ADDRSTRLEN);
|
inet_ntop(AF_INET, &addr.sin_addr, client_ip, INET_ADDRSTRLEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step 1: Present login screen and get credentials */
|
/* Read first message to detect protocol type */
|
||||||
LOG(LOG_INFO, "Presenting login screen to client");
|
memset(buffer, 0, sizeof(buffer));
|
||||||
if (present_login_screen(client_fd, username, password) != 0) {
|
ret = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
|
||||||
LOG(LOG_ERROR, "Failed to get login credentials");
|
if (ret <= 0) {
|
||||||
|
LOG(LOG_ERROR, "Failed to receive initial message");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step 2: Authenticate user */
|
/* Check if this is AUTH protocol (web-gateway) */
|
||||||
LOG(LOG_INFO, "Authenticating user: %s from %s", username, client_ip);
|
if (strncmp(buffer, "AUTH\n", 5) == 0) {
|
||||||
if (authenticate_user(username, password, config->samba_server,
|
LOG(LOG_INFO, "Detected AUTH protocol from web-gateway");
|
||||||
config->samba_port, config->base_dn) != 0) {
|
return handle_auth_protocol(client_fd, buffer, ret, config, sm, client_ip);
|
||||||
LOG(LOG_WARN, "Authentication failed for user: %s", username);
|
} else {
|
||||||
const char *msg = "Authentication failed\n";
|
/* Legacy RDP protocol - not supported anymore */
|
||||||
|
LOG(LOG_WARN, "Legacy RDP protocol not supported, use web-gateway");
|
||||||
|
const char *msg = "ERROR: Please use web-gateway for access\n";
|
||||||
send(client_fd, msg, strlen(msg), 0);
|
send(client_fd, msg, strlen(msg), 0);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG(LOG_INFO, "User %s authenticated successfully", username);
|
|
||||||
|
|
||||||
/* Step 3: Present target menu */
|
|
||||||
LOG(LOG_INFO, "Presenting target menu to user: %s", username);
|
|
||||||
if (present_target_menu(client_fd, config, &selected_target) != 0) {
|
|
||||||
LOG(LOG_ERROR, "Failed to get target selection");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selected_target) {
|
|
||||||
LOG(LOG_ERROR, "No target selected");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG(LOG_INFO, "User %s selected target: %s (%s:%d)", username,
|
|
||||||
selected_target->name, selected_target->host, selected_target->port);
|
|
||||||
|
|
||||||
/* Step 4: Create session */
|
|
||||||
session = create_session(sm, username, client_ip, selected_target, client_fd);
|
|
||||||
if (!session) {
|
|
||||||
LOG(LOG_ERROR, "Failed to create session");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG(LOG_INFO, "Created session %u for %s -> %s", session->session_id,
|
|
||||||
username, selected_target->name);
|
|
||||||
|
|
||||||
/* Step 5: Forward RDP connection */
|
|
||||||
LOG(LOG_INFO, "Forwarding RDP connection for session %u", session->session_id);
|
|
||||||
if (forward_rdp_connection(client_fd, selected_target, session) != 0) {
|
|
||||||
LOG(LOG_ERROR, "Failed to forward RDP connection");
|
|
||||||
close_session(sm, session->session_id);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Connection closed */
|
|
||||||
LOG(LOG_INFO, "Session %u ended", session->session_id);
|
|
||||||
close_session(sm, session->session_id);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int present_login_screen(int client_fd, char *username, char *password) {
|
int present_login_screen(int client_fd, char *username, char *password) {
|
||||||
@@ -307,8 +451,8 @@ int present_target_menu(int client_fd, broker_config_t *config,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int forward_rdp_connection(int client_fd, rdp_target_t *target,
|
int forward_rdp_connection(int client_fd, const char *target_host,
|
||||||
session_info_t *session) {
|
int target_port, session_info_t *session) {
|
||||||
int target_fd;
|
int target_fd;
|
||||||
struct sockaddr_in target_addr;
|
struct sockaddr_in target_addr;
|
||||||
fd_set read_fds;
|
fd_set read_fds;
|
||||||
@@ -325,10 +469,10 @@ int forward_rdp_connection(int client_fd, rdp_target_t *target,
|
|||||||
|
|
||||||
memset(&target_addr, 0, sizeof(target_addr));
|
memset(&target_addr, 0, sizeof(target_addr));
|
||||||
target_addr.sin_family = AF_INET;
|
target_addr.sin_family = AF_INET;
|
||||||
target_addr.sin_port = htons(target->port);
|
target_addr.sin_port = htons(target_port);
|
||||||
|
|
||||||
if (inet_pton(AF_INET, target->host, &target_addr.sin_addr) <= 0) {
|
if (inet_pton(AF_INET, target_host, &target_addr.sin_addr) <= 0) {
|
||||||
LOG(LOG_ERROR, "Invalid target address: %s", target->host);
|
LOG(LOG_ERROR, "Invalid target address: %s", target_host);
|
||||||
close(target_fd);
|
close(target_fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -336,38 +480,53 @@ int forward_rdp_connection(int client_fd, rdp_target_t *target,
|
|||||||
if (connect(target_fd, (struct sockaddr *)&target_addr,
|
if (connect(target_fd, (struct sockaddr *)&target_addr,
|
||||||
sizeof(target_addr)) < 0) {
|
sizeof(target_addr)) < 0) {
|
||||||
LOG(LOG_ERROR, "Failed to connect to target %s:%d: %s",
|
LOG(LOG_ERROR, "Failed to connect to target %s:%d: %s",
|
||||||
target->host, target->port, strerror(errno));
|
target_host, target_port, strerror(errno));
|
||||||
close(target_fd);
|
close(target_fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG(LOG_INFO, "Connected to target %s:%d", target->host, target->port);
|
LOG(LOG_INFO, "Connected to target %s:%d", target_host, target_port);
|
||||||
session->target_fd = target_fd;
|
session->target_fd = target_fd;
|
||||||
|
|
||||||
const char *msg = "Connected to target. Starting RDP session...\n";
|
/* Forward data bidirectionally - no messages sent, just raw RDP forwarding */
|
||||||
send(client_fd, msg, strlen(msg), 0);
|
|
||||||
|
|
||||||
/* Forward data bidirectionally */
|
|
||||||
max_fd = (client_fd > target_fd) ? client_fd : target_fd;
|
max_fd = (client_fd > target_fd) ? client_fd : target_fd;
|
||||||
|
|
||||||
while (1) {
|
LOG(LOG_INFO, "Starting bidirectional forwarding loop (client_fd=%d, target_fd=%d)",
|
||||||
|
client_fd, target_fd);
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
struct timeval timeout;
|
||||||
|
timeout.tv_sec = 1;
|
||||||
|
timeout.tv_usec = 0;
|
||||||
|
|
||||||
FD_ZERO(&read_fds);
|
FD_ZERO(&read_fds);
|
||||||
FD_SET(client_fd, &read_fds);
|
FD_SET(client_fd, &read_fds);
|
||||||
FD_SET(target_fd, &read_fds);
|
FD_SET(target_fd, &read_fds);
|
||||||
|
|
||||||
if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) {
|
int sel_ret = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
|
||||||
|
if (sel_ret < 0) {
|
||||||
LOG(LOG_ERROR, "Select failed: %s", strerror(errno));
|
LOG(LOG_ERROR, "Select failed: %s", strerror(errno));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sel_ret == 0) {
|
||||||
|
/* Timeout - continue loop to check running flag */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* Data from client to target */
|
/* Data from client to target */
|
||||||
if (FD_ISSET(client_fd, &read_fds)) {
|
if (FD_ISSET(client_fd, &read_fds)) {
|
||||||
n = recv(client_fd, buffer, sizeof(buffer), 0);
|
n = recv(client_fd, buffer, sizeof(buffer), 0);
|
||||||
if (n <= 0) {
|
if (n < 0) {
|
||||||
LOG(LOG_DEBUG, "Client connection closed");
|
LOG(LOG_ERROR, "Error receiving from client: %s", strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (n == 0) {
|
||||||
|
LOG(LOG_INFO, "Client connection closed gracefully");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG(LOG_DEBUG, "Forwarding %d bytes from client to target", n);
|
||||||
if (send(target_fd, buffer, n, 0) != n) {
|
if (send(target_fd, buffer, n, 0) != n) {
|
||||||
LOG(LOG_ERROR, "Failed to forward to target");
|
LOG(LOG_ERROR, "Failed to forward to target");
|
||||||
break;
|
break;
|
||||||
@@ -379,11 +538,16 @@ int forward_rdp_connection(int client_fd, rdp_target_t *target,
|
|||||||
/* Data from target to client */
|
/* Data from target to client */
|
||||||
if (FD_ISSET(target_fd, &read_fds)) {
|
if (FD_ISSET(target_fd, &read_fds)) {
|
||||||
n = recv(target_fd, buffer, sizeof(buffer), 0);
|
n = recv(target_fd, buffer, sizeof(buffer), 0);
|
||||||
if (n <= 0) {
|
if (n < 0) {
|
||||||
LOG(LOG_DEBUG, "Target connection closed");
|
LOG(LOG_ERROR, "Error receiving from target: %s", strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (n == 0) {
|
||||||
|
LOG(LOG_INFO, "Target connection closed gracefully");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG(LOG_DEBUG, "Forwarding %d bytes from target to client", n);
|
||||||
if (send(client_fd, buffer, n, 0) != n) {
|
if (send(client_fd, buffer, n, 0) != n) {
|
||||||
LOG(LOG_ERROR, "Failed to forward to client");
|
LOG(LOG_ERROR, "Failed to forward to client");
|
||||||
break;
|
break;
|
||||||
|
|||||||
2
src/test.sh
Executable file
2
src/test.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
docker container run -it -e SAMBA_AD_SERVER=192.168.100.240 -e SAMBA_AD_BASE_DN="DC=aipice,DC=local" -v /data/apps/RdpBroker/targets.yaml:/etc/rdpbroker/targets.yaml -p 3389:3389 easylinux/rdp-broker:1.0-1 sh
|
||||||
@@ -16,7 +16,7 @@ targets:
|
|||||||
|
|
||||||
# Development Desktop
|
# Development Desktop
|
||||||
- name: "Development Desktop"
|
- name: "Development Desktop"
|
||||||
host: "dev-machine.local"
|
host: "192.168.100.135"
|
||||||
port: 3389
|
port: 3389
|
||||||
description: "Developer Workstation"
|
description: "Developer Workstation"
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
"cors": "^2.8.5"
|
"cors": "^2.8.5",
|
||||||
|
"node-rdpjs-2": "^0.3.4",
|
||||||
|
"pngjs": "^7.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.2"
|
"nodemon": "^3.0.2"
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ class RDPWebGateway {
|
|||||||
await this.authenticateAndLoadTargets();
|
await this.authenticateAndLoadTargets();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login error:', error);
|
console.error('Login error:', error);
|
||||||
this.showError(errorMessage, 'Connection error. Please check your network and try again.');
|
// Show specific error message if available
|
||||||
|
const errorMsg = error.message || 'Connection error. Please check your network and try again.';
|
||||||
|
this.showError(errorMessage, errorMsg);
|
||||||
loginBtn.disabled = false;
|
loginBtn.disabled = false;
|
||||||
btnText.style.display = 'block';
|
btnText.style.display = 'block';
|
||||||
spinner.style.display = 'none';
|
spinner.style.display = 'none';
|
||||||
@@ -244,7 +246,7 @@ class RDPWebGateway {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleWebSocketMessage(event) {
|
handleWebSocketMessage(event) {
|
||||||
if (typeof event.data === 'string') {
|
try {
|
||||||
const message = JSON.parse(event.data);
|
const message = JSON.parse(event.data);
|
||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
@@ -252,30 +254,44 @@ class RDPWebGateway {
|
|||||||
document.getElementById('loadingOverlay').style.display = 'none';
|
document.getElementById('loadingOverlay').style.display = 'none';
|
||||||
document.getElementById('connectionInfo').textContent =
|
document.getElementById('connectionInfo').textContent =
|
||||||
`Connected to ${this.currentTarget.name}`;
|
`Connected to ${this.currentTarget.name}`;
|
||||||
|
console.log('RDP connection established');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'screen':
|
||||||
|
// Render screen update (PNG image data)
|
||||||
|
this.renderScreenUpdate(message);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'disconnected':
|
||||||
|
this.showConnectionError('RDP connection closed');
|
||||||
|
break;
|
||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
this.showConnectionError(message.error);
|
this.showConnectionError(message.error);
|
||||||
break;
|
break;
|
||||||
case 'resize':
|
|
||||||
this.canvas.width = message.width;
|
default:
|
||||||
this.canvas.height = message.height;
|
console.warn('Unknown message type:', message.type);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} catch (error) {
|
||||||
// Binary data - frame update
|
console.error('Error handling WebSocket message:', error);
|
||||||
this.renderFrame(event.data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFrame(data) {
|
renderScreenUpdate(update) {
|
||||||
// This is a simplified version
|
try {
|
||||||
// In production, you'd decode the RDP frame data properly
|
// Decode base64 PNG image
|
||||||
const imageData = new Uint8ClampedArray(data);
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
if (imageData.length === this.canvas.width * this.canvas.height * 4) {
|
// Draw image to canvas at specified position
|
||||||
const imgData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
|
this.ctx.drawImage(img, update.x, update.y, update.width, update.height);
|
||||||
imgData.data.set(imageData);
|
};
|
||||||
this.ctx.putImageData(imgData, 0, 0);
|
img.onerror = (error) => {
|
||||||
|
console.error('Failed to load screen update image:', error);
|
||||||
|
};
|
||||||
|
img.src = 'data:image/png;base64,' + update.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error rendering screen update:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,13 +342,18 @@ class RDPWebGateway {
|
|||||||
const x = Math.floor((event.clientX - rect.left) * (this.canvas.width / rect.width));
|
const x = Math.floor((event.clientX - rect.left) * (this.canvas.width / rect.width));
|
||||||
const y = Math.floor((event.clientY - rect.top) * (this.canvas.height / rect.height));
|
const y = Math.floor((event.clientY - rect.top) * (this.canvas.height / rect.height));
|
||||||
|
|
||||||
|
// Map button: 0=left, 1=middle, 2=right
|
||||||
|
let button = 0;
|
||||||
|
if (type === 'down' || type === 'up') {
|
||||||
|
button = event.button;
|
||||||
|
}
|
||||||
|
|
||||||
this.ws.send(JSON.stringify({
|
this.ws.send(JSON.stringify({
|
||||||
type: 'mouse',
|
type: 'mouse',
|
||||||
action: type,
|
action: type,
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
button: event.button,
|
button: button
|
||||||
deltaY: event.deltaY || 0,
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,8 +363,9 @@ class RDPWebGateway {
|
|||||||
this.ws.send(JSON.stringify({
|
this.ws.send(JSON.stringify({
|
||||||
type: 'keyboard',
|
type: 'keyboard',
|
||||||
action: type,
|
action: type,
|
||||||
key: event.key,
|
code: event.keyCode || event.which
|
||||||
code: event.code,
|
}));
|
||||||
|
}
|
||||||
keyCode: event.keyCode,
|
keyCode: event.keyCode,
|
||||||
ctrlKey: event.ctrlKey,
|
ctrlKey: event.ctrlKey,
|
||||||
altKey: event.altKey,
|
altKey: event.altKey,
|
||||||
@@ -438,3 +460,4 @@ class RDPWebGateway {
|
|||||||
|
|
||||||
// Initialize the app
|
// Initialize the app
|
||||||
const rdpGateway = new RDPWebGateway();
|
const rdpGateway = new RDPWebGateway();
|
||||||
|
// v1.4
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class RDPProxyHandler {
|
|||||||
this.rdpSocket = null;
|
this.rdpSocket = null;
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
this.isAuthenticated = false;
|
this.isAuthenticated = false;
|
||||||
|
this.isRdpMode = false; // Track when in binary RDP forwarding mode
|
||||||
this.dataBuffer = '';
|
this.dataBuffer = '';
|
||||||
this.pendingTarget = null;
|
this.pendingTarget = null;
|
||||||
}
|
}
|
||||||
@@ -84,7 +85,15 @@ class RDPProxyHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleBrokerData(data) {
|
handleBrokerData(data) {
|
||||||
// Accumulate data in buffer
|
// If in RDP mode, forward binary data directly
|
||||||
|
if (this.isRdpMode) {
|
||||||
|
if (this.ws.readyState === 1) {
|
||||||
|
this.ws.send(data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, accumulate and parse JSON messages
|
||||||
this.dataBuffer += data.toString();
|
this.dataBuffer += data.toString();
|
||||||
|
|
||||||
// Check if we have complete message (ends with \n\n or specific delimiter)
|
// Check if we have complete message (ends with \n\n or specific delimiter)
|
||||||
@@ -98,13 +107,6 @@ class RDPProxyHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If already authenticated and in RDP session, forward raw data to WebSocket
|
|
||||||
if (this.isAuthenticated && this.pendingTarget === null) {
|
|
||||||
if (this.ws.readyState === 1) {
|
|
||||||
this.ws.send(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processBrokerMessage(message) {
|
processBrokerMessage(message) {
|
||||||
@@ -137,6 +139,8 @@ class RDPProxyHandler {
|
|||||||
target: this.pendingTarget
|
target: this.pendingTarget
|
||||||
}));
|
}));
|
||||||
this.pendingTarget = null;
|
this.pendingTarget = null;
|
||||||
|
this.isRdpMode = true; // Switch to binary RDP forwarding mode
|
||||||
|
this.dataBuffer = ''; // Clear JSON buffer
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.warn(`${new Date().toISOString()} [WARN] Unknown broker message type:`, data.type);
|
console.warn(`${new Date().toISOString()} [WARN] Unknown broker message type:`, data.type);
|
||||||
|
|||||||
355
web-gateway/src/rdpProxyHandler.new.js
Normal file
355
web-gateway/src/rdpProxyHandler.new.js
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
const net = require('net');
|
||||||
|
const rdp = require('node-rdpjs-2');
|
||||||
|
const { PNG } = require('pngjs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean RDP Proxy Handler - v1.3
|
||||||
|
* Handles: Authentication with broker → Target selection → RDP connection with screen updates
|
||||||
|
*/
|
||||||
|
class RDPProxyHandler {
|
||||||
|
constructor(websocket, rdpBrokerHost, rdpBrokerPort) {
|
||||||
|
this.ws = websocket;
|
||||||
|
this.brokerHost = rdpBrokerHost;
|
||||||
|
this.brokerPort = rdpBrokerPort;
|
||||||
|
|
||||||
|
// State
|
||||||
|
this.state = 'INIT'; // INIT → AUTH → TARGETS → CONNECTED
|
||||||
|
this.username = null;
|
||||||
|
this.currentTarget = null;
|
||||||
|
|
||||||
|
// Promise callbacks for async operations
|
||||||
|
this.authResolve = null;
|
||||||
|
this.authReject = null;
|
||||||
|
|
||||||
|
// Broker connection (for AUTH/SELECT protocol)
|
||||||
|
this.brokerSocket = null;
|
||||||
|
this.brokerBuffer = '';
|
||||||
|
|
||||||
|
// RDP client (for actual RDP connection to target)
|
||||||
|
this.rdpClient = null;
|
||||||
|
this.rdpConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle incoming WebSocket message from browser
|
||||||
|
*/
|
||||||
|
async handleMessage(message) {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(message);
|
||||||
|
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'authenticate':
|
||||||
|
await this.handleAuthenticate(msg.username, msg.password);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'connect':
|
||||||
|
await this.handleConnectToTarget(msg.target);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mouse':
|
||||||
|
this.handleMouseInput(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'keyboard':
|
||||||
|
this.handleKeyboardInput(msg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn(`Unknown message type: ${msg.type}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling message:', error);
|
||||||
|
this.sendError('Failed to process message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 1: Authenticate with RdpBroker
|
||||||
|
*/
|
||||||
|
async handleAuthenticate(username, password) {
|
||||||
|
console.log(`[AUTH] Authenticating user: ${username}`);
|
||||||
|
this.username = username;
|
||||||
|
this.state = 'AUTH';
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.authResolve = resolve;
|
||||||
|
this.authReject = reject;
|
||||||
|
|
||||||
|
this.brokerSocket = new net.Socket();
|
||||||
|
|
||||||
|
this.brokerSocket.connect(this.brokerPort, this.brokerHost, () => {
|
||||||
|
console.log('[AUTH] Connected to broker');
|
||||||
|
// Send AUTH protocol message
|
||||||
|
const authMsg = `AUTH\n${username}\n${password}\n`;
|
||||||
|
this.brokerSocket.write(authMsg);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.brokerSocket.on('data', (data) => {
|
||||||
|
this.brokerBuffer += data.toString();
|
||||||
|
|
||||||
|
// Check for complete JSON message (ends with \n\n)
|
||||||
|
if (this.brokerBuffer.includes('\n\n')) {
|
||||||
|
const messages = this.brokerBuffer.split('\n\n');
|
||||||
|
this.brokerBuffer = messages.pop();
|
||||||
|
|
||||||
|
messages.forEach(msgText => {
|
||||||
|
if (msgText.trim()) {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(msgText.trim());
|
||||||
|
this.handleBrokerMessage(msg);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse broker message:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.brokerSocket.on('error', (error) => {
|
||||||
|
console.error('[AUTH] Broker connection error:', error);
|
||||||
|
this.sendError('Failed to connect to authentication server');
|
||||||
|
if (this.authReject) {
|
||||||
|
this.authReject(error);
|
||||||
|
this.authReject = null;
|
||||||
|
this.authResolve = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.brokerSocket.on('close', () => {
|
||||||
|
console.log('[AUTH] Broker connection closed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle messages from RdpBroker (auth results, target lists, etc)
|
||||||
|
*/
|
||||||
|
handleBrokerMessage(msg) {
|
||||||
|
console.log('[BROKER] Received:', msg.type);
|
||||||
|
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'auth_success':
|
||||||
|
console.log(`[AUTH] Success - ${msg.targets.length} targets available`);
|
||||||
|
this.state = 'TARGETS';
|
||||||
|
this.sendToClient({
|
||||||
|
type: 'targets',
|
||||||
|
targets: msg.targets
|
||||||
|
});
|
||||||
|
// Resolve the authentication promise
|
||||||
|
if (this.authResolve) {
|
||||||
|
this.authResolve();
|
||||||
|
this.authResolve = null;
|
||||||
|
this.authReject = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'auth_failed':
|
||||||
|
console.log('[AUTH] Failed:', msg.message);
|
||||||
|
this.sendError(msg.message || 'Authentication failed');
|
||||||
|
// Reject the authentication promise
|
||||||
|
if (this.authReject) {
|
||||||
|
this.authReject(new Error(msg.message));
|
||||||
|
this.authResolve = null;
|
||||||
|
this.authReject = null;
|
||||||
|
}
|
||||||
|
this.cleanup();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rdp_ready':
|
||||||
|
console.log('[BROKER] RDP session ready signal received');
|
||||||
|
// Broker has connected to target, but we won't use this connection
|
||||||
|
// We'll create our own RDP client connection
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn('[BROKER] Unknown message type:', msg.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Connect to selected target via RDP
|
||||||
|
*/
|
||||||
|
async handleConnectToTarget(target) {
|
||||||
|
if (this.state !== 'TARGETS') {
|
||||||
|
this.sendError('Must authenticate first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[RDP] Connecting to target: ${target.name}`);
|
||||||
|
this.currentTarget = target;
|
||||||
|
|
||||||
|
// Send SELECT message to broker (to maintain session tracking)
|
||||||
|
const selectMsg = `SELECT\n${target.name}\n`;
|
||||||
|
if (this.brokerSocket && !this.brokerSocket.destroyed) {
|
||||||
|
this.brokerSocket.write(selectMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create direct RDP connection to target
|
||||||
|
await this.connectRDP(target.host, target.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create RDP client connection and handle screen updates
|
||||||
|
*/
|
||||||
|
async connectRDP(host, port) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
console.log(`[RDP] Creating client for ${host}:${port}`);
|
||||||
|
|
||||||
|
this.rdpClient = rdp.createClient({
|
||||||
|
domain: '',
|
||||||
|
userName: this.username,
|
||||||
|
password: '', // Already authenticated via broker
|
||||||
|
enablePerf: true,
|
||||||
|
autoLogin: true,
|
||||||
|
screen: { width: 1024, height: 768 },
|
||||||
|
locale: 'en',
|
||||||
|
logLevel: 'INFO'
|
||||||
|
}).on('connect', () => {
|
||||||
|
console.log('[RDP] Connected');
|
||||||
|
this.rdpConnected = true;
|
||||||
|
this.state = 'CONNECTED';
|
||||||
|
|
||||||
|
this.sendToClient({
|
||||||
|
type: 'connected',
|
||||||
|
target: this.currentTarget
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
|
||||||
|
}).on('bitmap', (bitmap) => {
|
||||||
|
// Received screen update from RDP server
|
||||||
|
this.handleScreenUpdate(bitmap);
|
||||||
|
|
||||||
|
}).on('close', () => {
|
||||||
|
console.log('[RDP] Connection closed');
|
||||||
|
this.rdpConnected = false;
|
||||||
|
this.sendToClient({ type: 'disconnected' });
|
||||||
|
|
||||||
|
}).on('error', (error) => {
|
||||||
|
console.error('[RDP] Error:', error);
|
||||||
|
this.sendError('RDP connection failed: ' + error.message);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to RDP server
|
||||||
|
this.rdpClient.connect(host, port);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[RDP] Failed to create client:', error);
|
||||||
|
this.sendError('Failed to initialize RDP client');
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle screen bitmap updates from RDP server
|
||||||
|
*/
|
||||||
|
handleScreenUpdate(bitmap) {
|
||||||
|
try {
|
||||||
|
// Convert bitmap to PNG and send to browser
|
||||||
|
const png = new PNG({
|
||||||
|
width: bitmap.width,
|
||||||
|
height: bitmap.height
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy bitmap data (assuming RGBA format)
|
||||||
|
png.data = Buffer.from(bitmap.data);
|
||||||
|
|
||||||
|
const buffer = PNG.sync.write(png);
|
||||||
|
const base64Data = buffer.toString('base64');
|
||||||
|
|
||||||
|
this.sendToClient({
|
||||||
|
type: 'screen',
|
||||||
|
x: bitmap.destLeft || 0,
|
||||||
|
y: bitmap.destTop || 0,
|
||||||
|
width: bitmap.width,
|
||||||
|
height: bitmap.height,
|
||||||
|
data: base64Data
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[RDP] Error processing screen update:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse input from browser
|
||||||
|
*/
|
||||||
|
handleMouseInput(msg) {
|
||||||
|
if (!this.rdpConnected || !this.rdpClient) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.rdpClient.sendPointerEvent(
|
||||||
|
msg.x,
|
||||||
|
msg.y,
|
||||||
|
msg.button || 0,
|
||||||
|
msg.action === 'down'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[RDP] Mouse input error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle keyboard input from browser
|
||||||
|
*/
|
||||||
|
handleKeyboardInput(msg) {
|
||||||
|
if (!this.rdpConnected || !this.rdpClient) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.rdpClient.sendKeyEventScancode(
|
||||||
|
msg.code,
|
||||||
|
msg.action === 'down'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[RDP] Keyboard input error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message to browser client
|
||||||
|
*/
|
||||||
|
sendToClient(message) {
|
||||||
|
if (this.ws.readyState === 1) {
|
||||||
|
this.ws.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send error to browser client
|
||||||
|
*/
|
||||||
|
sendError(message) {
|
||||||
|
this.sendToClient({
|
||||||
|
type: 'error',
|
||||||
|
error: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup all connections
|
||||||
|
*/
|
||||||
|
cleanup() {
|
||||||
|
console.log('[CLEANUP] Closing connections');
|
||||||
|
|
||||||
|
if (this.rdpClient) {
|
||||||
|
try {
|
||||||
|
this.rdpClient.close();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[CLEANUP] Error closing RDP client:', e);
|
||||||
|
}
|
||||||
|
this.rdpClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.brokerSocket && !this.brokerSocket.destroyed) {
|
||||||
|
this.brokerSocket.destroy();
|
||||||
|
this.brokerSocket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rdpConnected = false;
|
||||||
|
this.state = 'INIT';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RDPProxyHandler;
|
||||||
@@ -6,7 +6,7 @@ const net = require('net');
|
|||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const RDPProxyHandler = require('./rdpProxyHandler');
|
const RDPProxyHandler = require('./rdpProxyHandler.new');
|
||||||
|
|
||||||
class RDPWebGatewayServer {
|
class RDPWebGatewayServer {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -111,8 +111,8 @@ class RDPWebGatewayServer {
|
|||||||
|
|
||||||
ws.on('message', (data) => {
|
ws.on('message', (data) => {
|
||||||
try {
|
try {
|
||||||
const message = JSON.parse(data.toString());
|
// All messages are JSON (no binary mode)
|
||||||
proxyHandler.handleMessage(message);
|
proxyHandler.handleMessage(data.toString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`${new Date().toISOString()} [ERROR] WebSocket message error:`, error);
|
console.error(`${new Date().toISOString()} [ERROR] WebSocket message error:`, error);
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
|
|||||||
Reference in New Issue
Block a user