Initialisation depot

This commit is contained in:
Serge NOEL
2026-02-10 12:12:11 +01:00
commit c3176e8d79
818 changed files with 52573 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
# .dockerignore
build/
bin/
*.o
*.so
*.a
.git/
.gitignore
README.md
docs/
chart/

50
RdpBroker/src/Dockerfile Normal file
View File

@@ -0,0 +1,50 @@
# Build stage
FROM alpine:3.22 AS builder
# Install build dependencies
RUN apk add --no-cache \
gcc \
musl-dev \
make \
openldap-dev \
yaml-dev
# Set working directory
WORKDIR /build
# Copy source files
COPY *.c *.h Makefile ./
# Build the application
RUN make deps-alpine && make
# Runtime stage
FROM alpine:3.22
# Install runtime dependencies
RUN apk add --no-cache \
libldap \
yaml \
ca-certificates
# Create app user
RUN addgroup -g 1000 rdpbroker && \
adduser -D -u 1000 -G rdpbroker rdpbroker
# Create necessary directories
RUN mkdir -p /etc/rdpbroker /var/log/rdpbroker && \
chown -R rdpbroker:rdpbroker /etc/rdpbroker /var/log/rdpbroker
# Copy binary from builder
COPY --from=builder /build/bin/rdpbroker /usr/local/bin/rdpbroker
# Set permissions
RUN chmod +x /usr/local/bin/rdpbroker
# Switch to non-root user
USER rdpbroker
# Expose RDP port
EXPOSE 3389
# Set entrypoint
ENTRYPOINT ["/usr/local/bin/rdpbroker"]

62
RdpBroker/src/Makefile Normal file
View File

@@ -0,0 +1,62 @@
# RdpBroker Makefile
CC = gcc
CFLAGS = -Wall -Wextra -O2 -pthread -D_GNU_SOURCE
LDFLAGS = -pthread -lldap -llber -lyaml
# Directories
SRC_DIR = .
BUILD_DIR = build
BIN_DIR = bin
# Source files
SOURCES = main.c config.c auth.c rdp_server.c session_manager.c
OBJECTS = $(SOURCES:%.c=$(BUILD_DIR)/%.o)
TARGET = $(BIN_DIR)/rdpbroker
# Default target
all: directories $(TARGET)
# Create necessary directories
directories:
@mkdir -p $(BUILD_DIR)
@mkdir -p $(BIN_DIR)
# Link
$(TARGET): $(OBJECTS)
@echo "Linking $(TARGET)..."
$(CC) $(OBJECTS) -o $(TARGET) $(LDFLAGS)
@echo "Build complete: $(TARGET)"
# Compile
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@echo "Compiling $<..."
$(CC) $(CFLAGS) -c $< -o $@
# Clean
clean:
@echo "Cleaning build files..."
rm -rf $(BUILD_DIR) $(BIN_DIR)
@echo "Clean complete"
# Install dependencies (Debian/Ubuntu)
deps-debian:
@echo "Installing dependencies for Debian/Ubuntu..."
apt-get update
apt-get install -y build-essential libldap2-dev libyaml-dev
# Install dependencies (Alpine - for Docker)
deps-alpine:
@echo "Installing dependencies for Alpine..."
apk add --no-cache gcc musl-dev make openldap-dev yaml-dev
# Run
run: $(TARGET)
@echo "Running RdpBroker..."
$(TARGET)
# Debug build
debug: CFLAGS += -g -DDEBUG
debug: clean all
.PHONY: all clean directories deps-debian deps-alpine run debug

109
RdpBroker/src/auth.c Normal file
View File

@@ -0,0 +1,109 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ldap.h>
#include "rdp_broker.h"
int authenticate_user(const char *username, const char *password,
const char *samba_server, int samba_port,
const char *base_dn) {
LOG(LOG_DEBUG, "Authenticating user: %s", username);
if (!username || !password || strlen(password) == 0) {
LOG(LOG_WARN, "Empty username or password");
return -1;
}
/* Perform LDAP bind to validate credentials */
int result = ldap_bind_check(samba_server, samba_port, username,
password, base_dn);
if (result == 0) {
LOG(LOG_INFO, "Authentication successful for user: %s", username);
return 0;
} else {
LOG(LOG_WARN, "Authentication failed for user: %s", username);
return -1;
}
}
int ldap_bind_check(const char *server, int port, const char *username,
const char *password, const char *base_dn) {
LDAP *ld = NULL;
int rc;
char ldap_uri[512];
char bind_dn[512];
int version = LDAP_VERSION3;
/* Construct LDAP URI */
snprintf(ldap_uri, sizeof(ldap_uri), "ldap://%s:%d", server, port);
/* Initialize LDAP connection */
rc = ldap_initialize(&ld, ldap_uri);
if (rc != LDAP_SUCCESS) {
LOG(LOG_ERROR, "LDAP initialization failed: %s", ldap_err2string(rc));
return -1;
}
/* Set LDAP version */
rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
if (rc != LDAP_OPT_SUCCESS) {
LOG(LOG_ERROR, "Failed to set LDAP version: %s", ldap_err2string(rc));
ldap_unbind_ext_s(ld, NULL, NULL);
return -1;
}
/* Construct bind DN - typically cn=username,base_dn or username@domain */
/* For Samba AD, we can use username@domain format or userPrincipalName */
/* Here we'll try simple bind with CN format first */
snprintf(bind_dn, sizeof(bind_dn), "cn=%s,%s", username, base_dn);
/* Attempt to bind */
struct berval cred;
cred.bv_val = (char *)password;
cred.bv_len = strlen(password);
rc = ldap_sasl_bind_s(ld, bind_dn, LDAP_SASL_SIMPLE, &cred,
NULL, NULL, NULL);
if (rc != LDAP_SUCCESS) {
/* Try alternative format: username@domain */
/* Extract domain from base_dn (DC=example,DC=com -> example.com) */
char domain[256] = {0};
const char *dc = strstr(base_dn, "DC=");
if (dc) {
const char *ptr = dc + 3;
char *out = domain;
while (*ptr && (out - domain) < sizeof(domain) - 1) {
if (*ptr == ',') {
ptr++;
if (strncmp(ptr, "DC=", 3) == 0) {
*out++ = '.';
ptr += 3;
continue;
}
break;
}
*out++ = *ptr++;
}
*out = '\0';
}
if (strlen(domain) > 0) {
snprintf(bind_dn, sizeof(bind_dn), "%s@%s", username, domain);
rc = ldap_sasl_bind_s(ld, bind_dn, LDAP_SASL_SIMPLE, &cred,
NULL, NULL, NULL);
}
}
ldap_unbind_ext_s(ld, NULL, NULL);
if (rc == LDAP_SUCCESS) {
LOG(LOG_DEBUG, "LDAP bind successful for: %s", username);
return 0;
} else {
LOG(LOG_DEBUG, "LDAP bind failed: %s", ldap_err2string(rc));
return -1;
}
}

162
RdpBroker/src/config.c Normal file
View File

@@ -0,0 +1,162 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yaml.h>
#include "rdp_broker.h"
int load_config(broker_config_t *config) {
const char *env_value;
/* Initialize config with defaults */
memset(config, 0, sizeof(broker_config_t));
/* Load from environment variables */
env_value = getenv("SAMBA_AD_SERVER");
if (env_value) {
strncpy(config->samba_server, env_value, MAX_HOSTNAME_LEN - 1);
} else {
LOG(LOG_ERROR, "SAMBA_AD_SERVER environment variable not set");
return -1;
}
env_value = getenv("SAMBA_AD_PORT");
config->samba_port = env_value ? atoi(env_value) : 389;
env_value = getenv("SAMBA_AD_BASE_DN");
if (env_value) {
strncpy(config->base_dn, env_value, MAX_PATH_LEN - 1);
} else {
LOG(LOG_ERROR, "SAMBA_AD_BASE_DN environment variable not set");
return -1;
}
env_value = getenv("RDP_LISTEN_PORT");
config->rdp_listen_port = env_value ? atoi(env_value) : 3389;
env_value = getenv("TARGETS_CONFIG_PATH");
if (env_value) {
strncpy(config->targets_config_path, env_value, MAX_PATH_LEN - 1);
} else {
strncpy(config->targets_config_path, "/etc/rdpbroker/targets.yaml",
MAX_PATH_LEN - 1);
}
env_value = getenv("LOG_LEVEL");
if (env_value) {
if (strcmp(env_value, "DEBUG") == 0) {
config->log_level = LOG_DEBUG;
} else if (strcmp(env_value, "INFO") == 0) {
config->log_level = LOG_INFO;
} else if (strcmp(env_value, "WARN") == 0) {
config->log_level = LOG_WARN;
} else if (strcmp(env_value, "ERROR") == 0) {
config->log_level = LOG_ERROR;
} else {
config->log_level = LOG_INFO;
}
} else {
config->log_level = LOG_INFO;
}
global_log_level = config->log_level;
/* Load targets configuration */
return load_targets(config, config->targets_config_path);
}
int load_targets(broker_config_t *config, const char *path) {
FILE *file;
yaml_parser_t parser;
yaml_event_t event;
int done = 0;
int in_targets = 0;
int in_target = 0;
char key[256] = {0};
rdp_target_t current_target;
memset(&current_target, 0, sizeof(rdp_target_t));
file = fopen(path, "r");
if (!file) {
LOG(LOG_ERROR, "Failed to open targets file: %s", path);
return -1;
}
if (!yaml_parser_initialize(&parser)) {
LOG(LOG_ERROR, "Failed to initialize YAML parser");
fclose(file);
return -1;
}
yaml_parser_set_input_file(&parser, file);
config->target_count = 0;
/* Simple YAML parsing - this is a basic implementation */
/* In production, use a more robust YAML library */
while (!done) {
if (!yaml_parser_parse(&parser, &event)) {
LOG(LOG_ERROR, "YAML parser error");
break;
}
switch (event.type) {
case YAML_SCALAR_EVENT:
if (strcmp((char *)event.data.scalar.value, "targets") == 0) {
in_targets = 1;
} else if (in_targets && strcmp(key, "name") == 0) {
strncpy(current_target.name,
(char *)event.data.scalar.value,
MAX_HOSTNAME_LEN - 1);
key[0] = '\0';
} else if (in_targets && strcmp(key, "host") == 0) {
strncpy(current_target.host,
(char *)event.data.scalar.value,
MAX_HOSTNAME_LEN - 1);
key[0] = '\0';
} else if (in_targets && strcmp(key, "port") == 0) {
current_target.port = atoi((char *)event.data.scalar.value);
key[0] = '\0';
} else if (in_targets && strcmp(key, "description") == 0) {
strncpy(current_target.description,
(char *)event.data.scalar.value,
MAX_DESCRIPTION_LEN - 1);
key[0] = '\0';
/* Target is complete, add it */
if (config->target_count < MAX_TARGETS) {
memcpy(&config->targets[config->target_count],
&current_target,
sizeof(rdp_target_t));
config->target_count++;
LOG(LOG_DEBUG, "Loaded target: %s (%s:%d)",
current_target.name, current_target.host,
current_target.port);
}
memset(&current_target, 0, sizeof(rdp_target_t));
} else if (in_targets) {
strncpy(key, (char *)event.data.scalar.value, sizeof(key) - 1);
}
break;
case YAML_STREAM_END_EVENT:
done = 1;
break;
default:
break;
}
yaml_event_delete(&event);
}
yaml_parser_delete(&parser);
fclose(file);
LOG(LOG_INFO, "Loaded %d targets from %s", config->target_count, path);
return 0;
}
void free_config(broker_config_t *config) {
/* Nothing to free for now, but placeholder for future use */
(void)config;
}

84
RdpBroker/src/main.c Normal file
View File

@@ -0,0 +1,84 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include "rdp_broker.h"
int global_log_level = LOG_INFO;
volatile bool running = true; /* Global running flag */
void signal_handler(int signum) {
if (signum == SIGINT || signum == SIGTERM) {
LOG(LOG_INFO, "Received signal %d, shutting down...", signum);
running = false;
}
}
void print_banner(void) {
printf("\n");
printf("╔═══════════════════════════════════════════════════════╗\n");
printf("║ RDP Broker v1.0.0 ║\n");
printf("║ RDP Connection Broker with Samba AD Auth ║\n");
printf("╚═══════════════════════════════════════════════════════╝\n");
printf("\n");
}
int main(int argc, char *argv[]) {
broker_config_t config;
session_manager_t session_manager;
int ret;
print_banner();
/* Install signal handlers */
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGPIPE, SIG_IGN); /* Ignore broken pipe signals */
/* Load configuration */
LOG(LOG_INFO, "Loading configuration...");
ret = load_config(&config);
if (ret != 0) {
LOG(LOG_ERROR, "Failed to load configuration");
return 1;
}
LOG(LOG_INFO, "Configuration loaded successfully");
LOG(LOG_INFO, "Samba AD Server: %s:%d", config.samba_server, config.samba_port);
LOG(LOG_INFO, "RDP Listen Port: %d", config.rdp_listen_port);
LOG(LOG_INFO, "Loaded %d target(s)", config.target_count);
/* Initialize session manager */
LOG(LOG_INFO, "Initializing session manager...");
ret = init_session_manager(&session_manager);
if (ret != 0) {
LOG(LOG_ERROR, "Failed to initialize session manager");
free_config(&config);
return 1;
}
/* Start RDP server */
LOG(LOG_INFO, "Starting RDP server on port %d...", config.rdp_listen_port);
ret = start_rdp_server(&config, &session_manager);
if (ret != 0) {
LOG(LOG_ERROR, "Failed to start RDP server");
free_config(&config);
return 1;
}
/* Main loop - monitor sessions */
LOG(LOG_INFO, "RDP Broker is running. Press Ctrl+C to stop.");
while (running) {
sleep(30); /* Log every 30 seconds */
log_active_sessions(&session_manager);
cleanup_inactive_sessions(&session_manager, 3600); /* 1 hour timeout */
}
/* Cleanup */
LOG(LOG_INFO, "Cleaning up...");
free_config(&config);
LOG(LOG_INFO, "RDP Broker stopped");
return 0;
}

114
RdpBroker/src/rdp_broker.h Normal file
View File

@@ -0,0 +1,114 @@
#ifndef RDP_BROKER_H
#define RDP_BROKER_H
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
/* Global running flag for graceful shutdown */
extern volatile bool running;
#define MAX_TARGETS 100
#define MAX_SESSIONS 1000
#define MAX_USERNAME_LEN 256
#define MAX_HOSTNAME_LEN 256
#define MAX_DESCRIPTION_LEN 512
#define MAX_PATH_LEN 1024
/* Configuration structures */
typedef struct {
char name[MAX_HOSTNAME_LEN];
char host[MAX_HOSTNAME_LEN];
int port;
char description[MAX_DESCRIPTION_LEN];
} rdp_target_t;
typedef struct {
char samba_server[MAX_HOSTNAME_LEN];
int samba_port;
char base_dn[MAX_PATH_LEN];
int rdp_listen_port;
char targets_config_path[MAX_PATH_LEN];
int log_level;
rdp_target_t targets[MAX_TARGETS];
int target_count;
} broker_config_t;
/* Session management structures */
typedef struct {
uint32_t session_id;
char username[MAX_USERNAME_LEN];
char client_ip[64];
char target_host[MAX_HOSTNAME_LEN];
int target_port;
time_t start_time;
time_t last_activity;
bool active;
int client_fd;
int target_fd;
} session_info_t;
typedef struct {
session_info_t sessions[MAX_SESSIONS];
int session_count;
uint32_t next_session_id;
} session_manager_t;
/* Function prototypes */
/* config.c */
int load_config(broker_config_t *config);
int load_targets(broker_config_t *config, const char *path);
void free_config(broker_config_t *config);
/* auth.c */
int authenticate_user(const char *username, const char *password,
const char *samba_server, int samba_port,
const char *base_dn);
int ldap_bind_check(const char *server, int port, const char *username,
const char *password, const char *base_dn);
/* rdp_server.c */
int start_rdp_server(broker_config_t *config, session_manager_t *sm);
int handle_rdp_connection(int client_fd, broker_config_t *config,
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_target_menu(int client_fd, broker_config_t *config,
rdp_target_t **selected_target);
int forward_rdp_connection(int client_fd, const char *target_host,
int target_port, session_info_t *session);
/* session_manager.c */
int init_session_manager(session_manager_t *sm);
session_info_t* create_session(session_manager_t *sm, const char *username,
const char *client_ip, rdp_target_t *target,
int client_fd);
int update_session_activity(session_info_t *session);
int close_session(session_manager_t *sm, uint32_t session_id);
void log_active_sessions(session_manager_t *sm);
void cleanup_inactive_sessions(session_manager_t *sm, int timeout_seconds);
/* Logging macros */
typedef enum {
LOG_DEBUG = 0,
LOG_INFO = 1,
LOG_WARN = 2,
LOG_ERROR = 3
} log_level_t;
extern int global_log_level;
#define LOG(level, ...) do { \
if (level >= global_log_level) { \
fprintf(stderr, "[%s] ", #level); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} \
} while(0)
#endif /* RDP_BROKER_H */

564
RdpBroker/src/rdp_server.c Normal file
View File

@@ -0,0 +1,564 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <errno.h>
#include "rdp_broker.h"
typedef struct {
int client_fd;
broker_config_t *config;
session_manager_t *session_mgr;
} connection_handler_args_t;
void *handle_connection_thread(void *arg);
int create_listening_socket(int port);
int start_rdp_server(broker_config_t *config, session_manager_t *sm) {
int server_fd;
int client_fd;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
pthread_t thread_id;
struct timeval timeout;
fd_set read_fds;
/* Create listening socket */
server_fd = create_listening_socket(config->rdp_listen_port);
if (server_fd < 0) {
LOG(LOG_ERROR, "Failed to create listening socket");
return -1;
}
LOG(LOG_INFO, "RDP server listening on port %d", config->rdp_listen_port);
/* Accept connections in a loop */
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);
if (client_fd < 0) {
if (!running) break; /* Shutting down */
LOG(LOG_ERROR, "Accept failed: %s", strerror(errno));
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
LOG(LOG_INFO, "New connection from %s:%d", client_ip,
ntohs(client_addr.sin_port));
/* Create thread to handle connection */
connection_handler_args_t *args = malloc(sizeof(connection_handler_args_t));
if (!args) {
LOG(LOG_ERROR, "Failed to allocate memory for connection handler");
close(client_fd);
continue;
}
args->client_fd = client_fd;
args->config = config;
args->session_mgr = sm;
if (pthread_create(&thread_id, NULL, handle_connection_thread, args) != 0) {
LOG(LOG_ERROR, "Failed to create thread: %s", strerror(errno));
free(args);
close(client_fd);
continue;
}
/* Detach thread so it cleans up automatically */
pthread_detach(thread_id);
}
/* Clean shutdown */
LOG(LOG_INFO, "Closing server socket...");
close(server_fd);
return 0;
}
void *handle_connection_thread(void *arg) {
connection_handler_args_t *args = (connection_handler_args_t *)arg;
int ret;
LOG(LOG_DEBUG, "Handling connection on fd %d", args->client_fd);
ret = handle_rdp_connection(args->client_fd, args->config, args->session_mgr);
if (ret != 0) {
LOG(LOG_WARN, "Connection handler failed");
}
close(args->client_fd);
free(args);
return NULL;
}
int create_listening_socket(int port) {
int server_fd;
struct sockaddr_in server_addr;
int opt = 1;
/* Create socket */
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
LOG(LOG_ERROR, "Socket creation failed: %s", strerror(errno));
return -1;
}
/* Set socket options */
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
LOG(LOG_ERROR, "Setsockopt failed: %s", strerror(errno));
close(server_fd);
return -1;
}
/* Bind socket */
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
LOG(LOG_ERROR, "Bind failed: %s", strerror(errno));
close(server_fd);
return -1;
}
/* Listen */
if (listen(server_fd, 10) < 0) {
LOG(LOG_ERROR, "Listen failed: %s", strerror(errno));
close(server_fd);
return -1;
}
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,
session_manager_t *sm) {
char buffer[4096];
char username[MAX_USERNAME_LEN] = {0};
char password[MAX_USERNAME_LEN] = {0};
rdp_target_t *selected_target = NULL;
session_info_t *session = NULL;
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
char client_ip[INET_ADDRSTRLEN] = {0};
int ret;
/* Get client IP */
if (getpeername(client_fd, (struct sockaddr *)&addr, &addr_len) == 0) {
inet_ntop(AF_INET, &addr.sin_addr, client_ip, INET_ADDRSTRLEN);
}
/* Read first message to detect protocol type */
memset(buffer, 0, sizeof(buffer));
ret = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (ret <= 0) {
LOG(LOG_ERROR, "Failed to receive initial message");
return -1;
}
/* Check if this is AUTH protocol (web-gateway) */
if (strncmp(buffer, "AUTH\n", 5) == 0) {
LOG(LOG_INFO, "Detected AUTH protocol from web-gateway");
return handle_auth_protocol(client_fd, buffer, ret, config, sm, client_ip);
} else {
/* 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);
return -1;
}
}
int present_login_screen(int client_fd, char *username, char *password) {
/* This is a simplified implementation */
/* In a real implementation, this would use RDP protocol to present a login UI */
/* For now, we'll use a simple text-based protocol */
const char *prompt_user = "RDP Broker Login\nUsername: ";
const char *prompt_pass = "Password: ";
char buffer[1024];
int n;
/* Send username prompt */
if (send(client_fd, prompt_user, strlen(prompt_user), 0) < 0) {
return -1;
}
/* Receive username */
n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) {
return -1;
}
buffer[n] = '\0';
/* Remove newline */
char *newline = strchr(buffer, '\n');
if (newline) *newline = '\0';
newline = strchr(buffer, '\r');
if (newline) *newline = '\0';
strncpy(username, buffer, MAX_USERNAME_LEN - 1);
/* Send password prompt */
if (send(client_fd, prompt_pass, strlen(prompt_pass), 0) < 0) {
return -1;
}
/* Receive password */
n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) {
return -1;
}
buffer[n] = '\0';
/* Remove newline */
newline = strchr(buffer, '\n');
if (newline) *newline = '\0';
newline = strchr(buffer, '\r');
if (newline) *newline = '\0';
strncpy(password, buffer, MAX_USERNAME_LEN - 1);
return 0;
}
int present_target_menu(int client_fd, broker_config_t *config,
rdp_target_t **selected_target) {
char buffer[4096];
char input[256];
int n, selection;
/* Build menu */
snprintf(buffer, sizeof(buffer), "\nAvailable RDP Targets:\n");
for (int i = 0; i < config->target_count; i++) {
char line[512];
snprintf(line, sizeof(line), "%d. %s - %s (%s:%d)\n",
i + 1,
config->targets[i].name,
config->targets[i].description,
config->targets[i].host,
config->targets[i].port);
strncat(buffer, line, sizeof(buffer) - strlen(buffer) - 1);
}
strncat(buffer, "\nSelect target (1-", sizeof(buffer) - strlen(buffer) - 1);
char num[16];
snprintf(num, sizeof(num), "%d): ", config->target_count);
strncat(buffer, num, sizeof(buffer) - strlen(buffer) - 1);
/* Send menu */
if (send(client_fd, buffer, strlen(buffer), 0) < 0) {
return -1;
}
/* Receive selection */
n = recv(client_fd, input, sizeof(input) - 1, 0);
if (n <= 0) {
return -1;
}
input[n] = '\0';
selection = atoi(input);
if (selection < 1 || selection > config->target_count) {
const char *msg = "Invalid selection\n";
send(client_fd, msg, strlen(msg), 0);
return -1;
}
*selected_target = &config->targets[selection - 1];
return 0;
}
int forward_rdp_connection(int client_fd, const char *target_host,
int target_port, session_info_t *session) {
int target_fd;
struct sockaddr_in target_addr;
fd_set read_fds;
int max_fd;
char buffer[8192];
int n;
/* Connect to target */
target_fd = socket(AF_INET, SOCK_STREAM, 0);
if (target_fd < 0) {
LOG(LOG_ERROR, "Failed to create target socket: %s", strerror(errno));
return -1;
}
memset(&target_addr, 0, sizeof(target_addr));
target_addr.sin_family = AF_INET;
target_addr.sin_port = htons(target_port);
if (inet_pton(AF_INET, target_host, &target_addr.sin_addr) <= 0) {
LOG(LOG_ERROR, "Invalid target address: %s", target_host);
close(target_fd);
return -1;
}
if (connect(target_fd, (struct sockaddr *)&target_addr,
sizeof(target_addr)) < 0) {
LOG(LOG_ERROR, "Failed to connect to target %s:%d: %s",
target_host, target_port, strerror(errno));
close(target_fd);
return -1;
}
LOG(LOG_INFO, "Connected to target %s:%d", target_host, target_port);
session->target_fd = target_fd;
/* Forward data bidirectionally - no messages sent, just raw RDP forwarding */
max_fd = (client_fd > target_fd) ? client_fd : target_fd;
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_SET(client_fd, &read_fds);
FD_SET(target_fd, &read_fds);
int sel_ret = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
if (sel_ret < 0) {
LOG(LOG_ERROR, "Select failed: %s", strerror(errno));
break;
}
if (sel_ret == 0) {
/* Timeout - continue loop to check running flag */
continue;
}
/* Data from client to target */
if (FD_ISSET(client_fd, &read_fds)) {
n = recv(client_fd, buffer, sizeof(buffer), 0);
if (n < 0) {
LOG(LOG_ERROR, "Error receiving from client: %s", strerror(errno));
break;
}
if (n == 0) {
LOG(LOG_INFO, "Client connection closed gracefully");
break;
}
LOG(LOG_DEBUG, "Forwarding %d bytes from client to target", n);
if (send(target_fd, buffer, n, 0) != n) {
LOG(LOG_ERROR, "Failed to forward to target");
break;
}
update_session_activity(session);
}
/* Data from target to client */
if (FD_ISSET(target_fd, &read_fds)) {
n = recv(target_fd, buffer, sizeof(buffer), 0);
if (n < 0) {
LOG(LOG_ERROR, "Error receiving from target: %s", strerror(errno));
break;
}
if (n == 0) {
LOG(LOG_INFO, "Target connection closed gracefully");
break;
}
LOG(LOG_DEBUG, "Forwarding %d bytes from target to client", n);
if (send(client_fd, buffer, n, 0) != n) {
LOG(LOG_ERROR, "Failed to forward to client");
break;
}
update_session_activity(session);
}
}
close(target_fd);
session->target_fd = -1;
return 0;
}

View File

@@ -0,0 +1,177 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include "rdp_broker.h"
static pthread_mutex_t session_mutex = PTHREAD_MUTEX_INITIALIZER;
int init_session_manager(session_manager_t *sm) {
if (!sm) {
return -1;
}
memset(sm, 0, sizeof(session_manager_t));
sm->next_session_id = 1;
sm->session_count = 0;
LOG(LOG_DEBUG, "Session manager initialized");
return 0;
}
session_info_t* create_session(session_manager_t *sm, const char *username,
const char *client_ip, rdp_target_t *target,
int client_fd) {
session_info_t *session = NULL;
pthread_mutex_lock(&session_mutex);
if (sm->session_count >= MAX_SESSIONS) {
LOG(LOG_ERROR, "Maximum sessions reached (%d)", MAX_SESSIONS);
pthread_mutex_unlock(&session_mutex);
return NULL;
}
/* Find free slot */
for (int i = 0; i < MAX_SESSIONS; i++) {
if (!sm->sessions[i].active) {
session = &sm->sessions[i];
break;
}
}
if (!session) {
LOG(LOG_ERROR, "No free session slot available");
pthread_mutex_unlock(&session_mutex);
return NULL;
}
/* Initialize session */
memset(session, 0, sizeof(session_info_t));
session->session_id = sm->next_session_id++;
strncpy(session->username, username, MAX_USERNAME_LEN - 1);
strncpy(session->client_ip, client_ip, sizeof(session->client_ip) - 1);
strncpy(session->target_host, target->host, MAX_HOSTNAME_LEN - 1);
session->target_port = target->port;
session->start_time = time(NULL);
session->last_activity = session->start_time;
session->active = true;
session->client_fd = client_fd;
session->target_fd = -1;
sm->session_count++;
LOG(LOG_INFO, "Created session %u: %s@%s -> %s:%d",
session->session_id, username, client_ip,
target->host, target->port);
pthread_mutex_unlock(&session_mutex);
return session;
}
int update_session_activity(session_info_t *session) {
if (!session || !session->active) {
return -1;
}
pthread_mutex_lock(&session_mutex);
session->last_activity = time(NULL);
pthread_mutex_unlock(&session_mutex);
return 0;
}
int close_session(session_manager_t *sm, uint32_t session_id) {
session_info_t *session = NULL;
pthread_mutex_lock(&session_mutex);
/* Find session */
for (int i = 0; i < MAX_SESSIONS; i++) {
if (sm->sessions[i].active && sm->sessions[i].session_id == session_id) {
session = &sm->sessions[i];
break;
}
}
if (!session) {
LOG(LOG_WARN, "Session %u not found", session_id);
pthread_mutex_unlock(&session_mutex);
return -1;
}
time_t duration = time(NULL) - session->start_time;
LOG(LOG_INFO, "Closing session %u: %s -> %s:%d (duration: %ld seconds)",
session->session_id, session->username,
session->target_host, session->target_port, duration);
session->active = false;
sm->session_count--;
pthread_mutex_unlock(&session_mutex);
return 0;
}
void log_active_sessions(session_manager_t *sm) {
int count = 0;
time_t now = time(NULL);
pthread_mutex_lock(&session_mutex);
LOG(LOG_INFO, "=== Active Sessions Report ===");
for (int i = 0; i < MAX_SESSIONS; i++) {
if (sm->sessions[i].active) {
session_info_t *s = &sm->sessions[i];
time_t duration = now - s->start_time;
time_t idle = now - s->last_activity;
LOG(LOG_INFO, "Session %u: %s@%s -> %s:%d | Duration: %lds | Idle: %lds",
s->session_id, s->username, s->client_ip,
s->target_host, s->target_port, duration, idle);
count++;
}
}
if (count == 0) {
LOG(LOG_INFO, "No active sessions");
} else {
LOG(LOG_INFO, "Total active sessions: %d", count);
}
LOG(LOG_INFO, "=============================");
pthread_mutex_unlock(&session_mutex);
}
void cleanup_inactive_sessions(session_manager_t *sm, int timeout_seconds) {
time_t now = time(NULL);
int cleaned = 0;
pthread_mutex_lock(&session_mutex);
for (int i = 0; i < MAX_SESSIONS; i++) {
if (sm->sessions[i].active) {
time_t idle = now - sm->sessions[i].last_activity;
if (idle > timeout_seconds) {
LOG(LOG_WARN, "Cleaning up inactive session %u (idle: %ld seconds)",
sm->sessions[i].session_id, idle);
sm->sessions[i].active = false;
sm->session_count--;
cleaned++;
}
}
}
if (cleaned > 0) {
LOG(LOG_INFO, "Cleaned up %d inactive session(s)", cleaned);
}
pthread_mutex_unlock(&session_mutex);
}

2
RdpBroker/src/test.sh Executable file
View 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