FreeRDP/server/proxy/pf_server.c
2023-01-12 22:54:25 +01:00

1074 lines
27 KiB
C

/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server
*
* Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
* Copyright 2019 Idan Freiberg <speidy@gmail.com>
* Copyright 2021 Armin Novak <anovak@thincast.com>
* Copyright 2021 Thincast Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/crt.h>
#include <winpr/ssl.h>
#include <winpr/path.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/winsock.h>
#include <winpr/thread.h>
#include <errno.h>
#include <freerdp/freerdp.h>
#include <freerdp/streamdump.h>
#include <freerdp/channels/wtsvc.h>
#include <freerdp/channels/channels.h>
#include <freerdp/channels/drdynvc.h>
#include <freerdp/build-config.h>
#include <freerdp/channels/rdpdr.h>
#include <freerdp/server/proxy/proxy_server.h>
#include <freerdp/server/proxy/proxy_log.h>
#include "pf_server.h"
#include "pf_channel.h"
#include <freerdp/server/proxy/proxy_config.h>
#include "pf_client.h"
#include <freerdp/server/proxy/proxy_context.h>
#include "pf_update.h"
#include "proxy_modules.h"
#include "pf_utils.h"
#include "channels/pf_channel_drdynvc.h"
#include "channels/pf_channel_rdpdr.h"
#define TAG PROXY_TAG("server")
typedef struct
{
HANDLE thread;
freerdp_peer* client;
} peer_thread_args;
static BOOL pf_server_parse_target_from_routing_token(rdpContext* context, char** target,
DWORD* port)
{
#define TARGET_MAX (100)
#define ROUTING_TOKEN_PREFIX "Cookie: msts="
char* colon;
size_t len;
DWORD routing_token_length;
const size_t prefix_len = strnlen(ROUTING_TOKEN_PREFIX, sizeof(ROUTING_TOKEN_PREFIX));
const char* routing_token = freerdp_nego_get_routing_token(context, &routing_token_length);
pServerContext* ps = (pServerContext*)context;
if (!routing_token)
return FALSE;
if ((routing_token_length <= prefix_len) || (routing_token_length >= TARGET_MAX))
{
PROXY_LOG_ERR(TAG, ps, "invalid routing token length: %" PRIu32 "", routing_token_length);
return FALSE;
}
len = routing_token_length - prefix_len;
*target = malloc(len + 1);
if (!(*target))
return FALSE;
CopyMemory(*target, routing_token + prefix_len, len);
*(*target + len) = '\0';
colon = strchr(*target, ':');
if (colon)
{
/* port is specified */
unsigned long p = strtoul(colon + 1, NULL, 10);
if (p > USHRT_MAX)
{
free(*target);
return FALSE;
}
*port = (DWORD)p;
*colon = '\0';
}
return TRUE;
}
static BOOL pf_server_get_target_info(rdpContext* context, rdpSettings* settings,
const proxyConfig* config)
{
pServerContext* ps = (pServerContext*)context;
proxyFetchTargetEventInfo ev = { 0 };
WINPR_ASSERT(settings);
WINPR_ASSERT(ps);
WINPR_ASSERT(ps->pdata);
ev.fetch_method = config->FixedTarget ? PROXY_FETCH_TARGET_METHOD_CONFIG
: PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO;
if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_SERVER_FETCH_TARGET_ADDR, ps->pdata,
&ev))
return FALSE;
switch (ev.fetch_method)
{
case PROXY_FETCH_TARGET_METHOD_DEFAULT:
case PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO:
return pf_server_parse_target_from_routing_token(context, &settings->ServerHostname,
&settings->ServerPort);
case PROXY_FETCH_TARGET_METHOD_CONFIG:
{
WINPR_ASSERT(config);
if (config->TargetPort > 0)
freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, config->TargetPort);
else
freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, 3389);
if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, config->TargetHost))
{
PROXY_LOG_ERR(TAG, ps, "strdup failed!");
return FALSE;
}
if (config->TargetUser)
freerdp_settings_set_string(settings, FreeRDP_Username, config->TargetUser);
if (config->TargetDomain)
freerdp_settings_set_string(settings, FreeRDP_Domain, config->TargetDomain);
if (config->TargetPassword)
freerdp_settings_set_string(settings, FreeRDP_Password, config->TargetPassword);
return TRUE;
}
case PROXY_FETCH_TARGET_USE_CUSTOM_ADDR:
{
if (!ev.target_address)
{
PROXY_LOG_ERR(TAG, ps,
"router: using CUSTOM_ADDR fetch method, but target_address == NULL");
return FALSE;
}
if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, ev.target_address))
{
PROXY_LOG_ERR(TAG, ps, "strdup failed!");
return FALSE;
}
free(ev.target_address);
settings->ServerPort = ev.target_port;
return TRUE;
}
default:
PROXY_LOG_ERR(TAG, ps, "unknown target fetch method: %d", ev.fetch_method);
return FALSE;
}
return TRUE;
}
static BOOL pf_server_setup_channels(freerdp_peer* peer)
{
char** accepted_channels = NULL;
size_t accepted_channels_count;
size_t i;
pServerContext* ps = (pServerContext*)peer->context;
accepted_channels = WTSGetAcceptedChannelNames(peer, &accepted_channels_count);
if (!accepted_channels)
return TRUE;
for (i = 0; i < accepted_channels_count; i++)
{
pServerStaticChannelContext* channelContext;
const char* cname = accepted_channels[i];
UINT16 channelId = WTSChannelGetId(peer, cname);
PROXY_LOG_INFO(TAG, ps, "Accepted channel: %s (%" PRIu16 ")", cname, channelId);
channelContext = StaticChannelContext_new(ps, cname, channelId);
if (!channelContext)
{
PROXY_LOG_ERR(TAG, ps, "error seting up channelContext for '%s'", cname);
return FALSE;
}
if (strcmp(cname, DRDYNVC_SVC_CHANNEL_NAME) == 0)
{
if (!pf_channel_setup_drdynvc(ps->pdata, channelContext))
{
PROXY_LOG_ERR(TAG, ps, "error while setting up dynamic channel");
StaticChannelContext_free(channelContext);
return FALSE;
}
}
else if (strcmp(cname, RDPDR_SVC_CHANNEL_NAME) == 0 &&
(channelContext->channelMode == PF_UTILS_CHANNEL_INTERCEPT))
{
if (!pf_channel_setup_rdpdr(ps, channelContext))
{
PROXY_LOG_ERR(TAG, ps, "error while setting up redirection channel");
StaticChannelContext_free(channelContext);
return FALSE;
}
}
else
{
if (!pf_channel_setup_generic(channelContext))
{
PROXY_LOG_ERR(TAG, ps, "error while setting up generic channel");
StaticChannelContext_free(channelContext);
return FALSE;
}
}
if (!HashTable_Insert(ps->channelsByFrontId, &channelContext->front_channel_id,
channelContext))
{
StaticChannelContext_free(channelContext);
PROXY_LOG_ERR(TAG, ps, "error inserting channelContext in byId table for '%s'", cname);
return FALSE;
}
}
free(accepted_channels);
return TRUE;
}
/* Event callbacks */
/**
* This callback is called when the entire connection sequence is done (as
* described in MS-RDPBCGR section 1.3)
*
* The server may start sending graphics output and receiving keyboard/mouse
* input after this callback returns.
*/
static BOOL pf_server_post_connect(freerdp_peer* peer)
{
pServerContext* ps;
pClientContext* pc;
rdpSettings* client_settings;
proxyData* pdata;
rdpSettings* frontSettings;
WINPR_ASSERT(peer);
ps = (pServerContext*)peer->context;
WINPR_ASSERT(ps);
frontSettings = peer->context->settings;
WINPR_ASSERT(frontSettings);
pdata = ps->pdata;
WINPR_ASSERT(pdata);
PROXY_LOG_INFO(TAG, ps, "Accepted client: %s", frontSettings->ClientHostname);
if (!pf_server_setup_channels(peer))
{
PROXY_LOG_ERR(TAG, ps, "error setting up channels");
return FALSE;
}
pc = pf_context_create_client_context(frontSettings);
if (pc == NULL)
{
PROXY_LOG_ERR(TAG, ps, "failed to create client context!");
return FALSE;
}
client_settings = pc->context.settings;
/* keep both sides of the connection in pdata */
proxy_data_set_client_context(pdata, pc);
if (!pf_server_get_target_info(peer->context, client_settings, pdata->config))
{
PROXY_LOG_INFO(TAG, ps, "pf_server_get_target_info failed!");
return FALSE;
}
PROXY_LOG_INFO(TAG, ps, "remote target is %s:%" PRIu16 "", client_settings->ServerHostname,
client_settings->ServerPort);
if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_POST_CONNECT, pdata, peer))
return FALSE;
/* Start a proxy's client in it's own thread */
if (!(pdata->client_thread = CreateThread(NULL, 0, pf_client_start, pc, 0, NULL)))
{
PROXY_LOG_ERR(TAG, ps, "failed to create client thread");
return FALSE;
}
return TRUE;
}
static BOOL pf_server_activate(freerdp_peer* peer)
{
pServerContext* ps;
proxyData* pdata;
rdpSettings* settings;
WINPR_ASSERT(peer);
ps = (pServerContext*)peer->context;
WINPR_ASSERT(ps);
pdata = ps->pdata;
WINPR_ASSERT(pdata);
settings = peer->context->settings;
settings->CompressionLevel = PACKET_COMPR_TYPE_RDP8;
if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_ACTIVATE, pdata, peer))
return FALSE;
return TRUE;
}
static BOOL pf_server_logon(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity,
BOOL automatic)
{
pServerContext* ps;
proxyData* pdata;
proxyServerPeerLogon info = { 0 };
WINPR_ASSERT(peer);
ps = (pServerContext*)peer->context;
WINPR_ASSERT(ps);
pdata = ps->pdata;
WINPR_ASSERT(pdata);
WINPR_ASSERT(identity);
info.identity = identity;
info.automatic = automatic;
if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PEER_LOGON, pdata, &info))
return FALSE;
return TRUE;
}
static BOOL pf_server_adjust_monitor_layout(freerdp_peer* peer)
{
WINPR_ASSERT(peer);
/* proxy as is, there's no need to do anything here */
return TRUE;
}
static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 channelId,
const BYTE* data, size_t size, UINT32 flags,
size_t totalSize)
{
pServerContext* ps;
pClientContext* pc;
proxyData* pdata;
const proxyConfig* config;
const pServerStaticChannelContext* channel;
UINT64 channelId64 = channelId;
WINPR_ASSERT(peer);
ps = (pServerContext*)peer->context;
WINPR_ASSERT(ps);
pdata = ps->pdata;
WINPR_ASSERT(pdata);
pc = pdata->pc;
config = pdata->config;
WINPR_ASSERT(config);
/*
* client side is not initialized yet, call original callback.
* this is probably a drdynvc message between peer and proxy server,
* which doesn't need to be proxied.
*/
if (!pc)
goto original_cb;
channel = HashTable_GetItemValue(ps->channelsByFrontId, &channelId64);
if (!channel)
{
PROXY_LOG_ERR(TAG, ps, "channel id=%" PRIu64 " not registered here, dropping", channelId64);
return TRUE;
}
WINPR_ASSERT(channel->onFrontData);
switch (channel->onFrontData(pdata, channel, data, size, flags, totalSize))
{
case PF_CHANNEL_RESULT_PASS:
{
proxyChannelDataEventInfo ev = { 0 };
ev.channel_id = channelId;
ev.channel_name = channel->channel_name;
ev.data = data;
ev.data_len = size;
ev.flags = flags;
ev.total_size = totalSize;
return IFCALLRESULT(TRUE, pc->sendChannelData, pc, &ev);
}
case PF_CHANNEL_RESULT_DROP:
return TRUE;
case PF_CHANNEL_RESULT_ERROR:
return FALSE;
}
original_cb:
WINPR_ASSERT(pdata->server_receive_channel_data_original);
return pdata->server_receive_channel_data_original(peer, channelId, data, size, flags,
totalSize);
}
static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer)
{
pServerContext* ps;
rdpSettings* settings;
proxyData* pdata;
const proxyConfig* config;
proxyServer* server;
WINPR_ASSERT(peer);
ps = (pServerContext*)peer->context;
if (!ps)
return FALSE;
settings = peer->context->settings;
WINPR_ASSERT(settings);
pdata = proxy_data_new();
if (!pdata)
return FALSE;
server = (proxyServer*)peer->ContextExtra;
WINPR_ASSERT(server);
proxy_data_set_server_context(pdata, ps);
pdata->module = server->module;
config = pdata->config = server->config;
/* currently not supporting GDI orders */
ZeroMemory(settings->OrderSupport, 32);
WINPR_ASSERT(peer->context->update);
peer->context->update->autoCalculateBitmapData = FALSE;
if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE))
return FALSE;
if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, config->GFX))
return FALSE;
if (pf_utils_is_passthrough(config))
{
if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
return FALSE;
}
if (!freerdp_settings_set_string(settings, FreeRDP_CertificateFile, config->CertificateFile) ||
!freerdp_settings_set_string(settings, FreeRDP_CertificateContent,
config->CertificateContent) ||
!freerdp_settings_set_string(settings, FreeRDP_PrivateKeyFile, config->PrivateKeyFile) ||
!freerdp_settings_set_string(settings, FreeRDP_PrivateKeyContent,
config->PrivateKeyContent))
{
PROXY_LOG_ERR(TAG, ps, "Memory allocation failed (strdup)");
return FALSE;
}
if (config->RemoteApp)
{
const UINT32 mask =
RAIL_LEVEL_SUPPORTED | RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED |
RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED | RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED |
RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED |
RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED | RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED |
RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED;
if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteApplicationSupportLevel, mask))
return FALSE;
if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAppLanguageBarSupported, TRUE))
return FALSE;
}
if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, config->ServerRdpSecurity))
return FALSE;
if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, config->ServerTlsSecurity))
return FALSE;
if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, config->ServerNlaSecurity))
return FALSE;
settings->EncryptionLevel = ENCRYPTION_LEVEL_CLIENT_COMPATIBLE;
if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
return FALSE;
if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE))
return FALSE;
if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE))
return FALSE;
if (!freerdp_settings_set_bool(settings, FreeRDP_DesktopResize, TRUE))
return FALSE;
if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
0xFFFFFF)) /* FIXME */
return FALSE;
peer->PostConnect = pf_server_post_connect;
peer->Activate = pf_server_activate;
peer->Logon = pf_server_logon;
peer->AdjustMonitorsLayout = pf_server_adjust_monitor_layout;
/* virtual channels receive data hook */
pdata->server_receive_channel_data_original = peer->ReceiveChannelData;
peer->ReceiveChannelData = pf_server_receive_channel_data_hook;
if (!stream_dump_register_handlers(peer->context, CONNECTION_STATE_NEGO, TRUE))
return FALSE;
return TRUE;
}
/**
* Handles an incoming client connection, to be run in it's own thread.
*
* arg is a pointer to a freerdp_peer representing the client.
*/
static DWORD WINAPI pf_server_handle_peer(LPVOID arg)
{
HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
DWORD tmp;
DWORD status;
pServerContext* ps = NULL;
proxyData* pdata = NULL;
freerdp_peer* client;
proxyServer* server;
size_t count;
peer_thread_args* args = arg;
WINPR_ASSERT(args);
client = args->client;
WINPR_ASSERT(client);
server = (proxyServer*)client->ContextExtra;
WINPR_ASSERT(server);
count = ArrayList_Count(server->peer_list);
if (!pf_context_init_server_context(client))
goto out_free_peer;
if (!pf_server_initialize_peer_connection(client))
goto out_free_peer;
ps = (pServerContext*)client->context;
WINPR_ASSERT(ps);
PROXY_LOG_DBG(TAG, ps, "Added peer, %" PRIuz " connected", count);
pdata = ps->pdata;
WINPR_ASSERT(pdata);
pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_INITIALIZE, pdata, client);
WINPR_ASSERT(client->Initialize);
client->Initialize(client);
PROXY_LOG_INFO(TAG, ps, "new connection: proxy address: %s, client address: %s",
pdata->config->Host, client->hostname);
pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_STARTED, pdata, client);
while (1)
{
HANDLE ChannelEvent = INVALID_HANDLE_VALUE;
DWORD eventCount = 0;
{
WINPR_ASSERT(client->GetEventHandles);
tmp = client->GetEventHandles(client, &eventHandles[eventCount],
ARRAYSIZE(eventHandles) - eventCount);
if (tmp == 0)
{
PROXY_LOG_ERR(TAG, ps, "Failed to get FreeRDP transport event handles");
break;
}
eventCount += tmp;
}
/* Main client event handling loop */
ChannelEvent = WTSVirtualChannelManagerGetEventHandle(ps->vcm);
WINPR_ASSERT(ChannelEvent && (ChannelEvent != INVALID_HANDLE_VALUE));
WINPR_ASSERT(pdata->abort_event && (pdata->abort_event != INVALID_HANDLE_VALUE));
eventHandles[eventCount++] = ChannelEvent;
eventHandles[eventCount++] = pdata->abort_event;
eventHandles[eventCount++] = server->stopEvent;
status = WaitForMultipleObjects(eventCount, eventHandles, FALSE,
1000); /* Do periodic polling to avoid client hang */
if (status == WAIT_FAILED)
{
PROXY_LOG_ERR(TAG, ps, "WaitForMultipleObjects failed (status: %" PRIu32 ")", status);
break;
}
WINPR_ASSERT(client->CheckFileDescriptor);
if (client->CheckFileDescriptor(client) != TRUE)
break;
if (WaitForSingleObject(ChannelEvent, 0) == WAIT_OBJECT_0)
{
if (!WTSVirtualChannelManagerCheckFileDescriptor(ps->vcm))
{
PROXY_LOG_ERR(TAG, ps, "WTSVirtualChannelManagerCheckFileDescriptor failure");
goto fail;
}
}
/* only disconnect after checking client's and vcm's file descriptors */
if (proxy_data_shall_disconnect(pdata))
{
PROXY_LOG_INFO(TAG, ps, "abort event is set, closing connection with peer %s",
client->hostname);
break;
}
if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
{
PROXY_LOG_INFO(TAG, ps, "Server shutting down, terminating peer");
break;
}
switch (WTSVirtualChannelManagerGetDrdynvcState(ps->vcm))
{
/* Dynamic channel status may have been changed after processing */
case DRDYNVC_STATE_NONE:
/* Initialize drdynvc channel */
if (!WTSVirtualChannelManagerCheckFileDescriptor(ps->vcm))
{
PROXY_LOG_ERR(TAG, ps, "Failed to initialize drdynvc channel");
goto fail;
}
break;
case DRDYNVC_STATE_READY:
if (WaitForSingleObject(ps->dynvcReady, 0) == WAIT_TIMEOUT)
{
SetEvent(ps->dynvcReady);
}
break;
default:
break;
}
}
fail:
PROXY_LOG_INFO(TAG, ps, "starting shutdown of connection");
PROXY_LOG_INFO(TAG, ps, "stopping proxy's client");
/* Abort the client. */
proxy_data_abort_connect(pdata);
pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_END, pdata, client);
PROXY_LOG_INFO(TAG, ps, "freeing server's channels");
WINPR_ASSERT(client->Close);
client->Close(client);
WINPR_ASSERT(client->Disconnect);
client->Disconnect(client);
out_free_peer:
PROXY_LOG_INFO(TAG, ps, "freeing proxy data");
if (pdata && pdata->client_thread)
{
proxy_data_abort_connect(pdata);
WaitForSingleObject(pdata->client_thread, INFINITE);
}
{
ArrayList_Lock(server->peer_list);
ArrayList_Remove(server->peer_list, args->thread);
count = ArrayList_Count(server->peer_list);
ArrayList_Unlock(server->peer_list);
}
PROXY_LOG_DBG(TAG, ps, "Removed peer, %" PRIuz " connected", count);
freerdp_peer_context_free(client);
freerdp_peer_free(client);
proxy_data_free(pdata);
#if defined(WITH_DEBUG_EVENTS)
DumpEventHandles();
#endif
free(args);
ExitThread(0);
return 0;
}
static BOOL pf_server_start_peer(freerdp_peer* client)
{
HANDLE hThread;
proxyServer* server;
peer_thread_args* args = calloc(1, sizeof(peer_thread_args));
if (!args)
return FALSE;
WINPR_ASSERT(client);
args->client = client;
server = (proxyServer*)client->ContextExtra;
WINPR_ASSERT(server);
hThread = CreateThread(NULL, 0, pf_server_handle_peer, args, CREATE_SUSPENDED, NULL);
if (!hThread)
return FALSE;
args->thread = hThread;
if (!ArrayList_Append(server->peer_list, hThread))
{
CloseHandle(hThread);
return FALSE;
}
return ResumeThread(hThread) != (DWORD)-1;
}
static BOOL pf_server_peer_accepted(freerdp_listener* listener, freerdp_peer* client)
{
WINPR_ASSERT(listener);
WINPR_ASSERT(client);
client->ContextExtra = listener->info;
return pf_server_start_peer(client);
}
BOOL pf_server_start(proxyServer* server)
{
WSADATA wsaData;
WINPR_ASSERT(server);
WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
goto error;
WINPR_ASSERT(server->config);
WINPR_ASSERT(server->listener);
WINPR_ASSERT(server->listener->Open);
if (!server->listener->Open(server->listener, server->config->Host, server->config->Port))
{
switch (errno)
{
case EADDRINUSE:
WLog_ERR(TAG, "failed to start listener: address already in use!");
break;
case EACCES:
WLog_ERR(TAG, "failed to start listener: insufficent permissions!");
break;
default:
WLog_ERR(TAG, "failed to start listener: errno=%d", errno);
break;
}
goto error;
}
return TRUE;
error:
WSACleanup();
return FALSE;
}
BOOL pf_server_start_from_socket(proxyServer* server, int socket)
{
WSADATA wsaData;
WINPR_ASSERT(server);
WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
goto error;
WINPR_ASSERT(server->listener);
WINPR_ASSERT(server->listener->OpenFromSocket);
if (!server->listener->OpenFromSocket(server->listener, socket))
{
switch (errno)
{
case EADDRINUSE:
WLog_ERR(TAG, "failed to start listener: address already in use!");
break;
case EACCES:
WLog_ERR(TAG, "failed to start listener: insufficent permissions!");
break;
default:
WLog_ERR(TAG, "failed to start listener: errno=%d", errno);
break;
}
goto error;
}
return TRUE;
error:
WSACleanup();
return FALSE;
}
BOOL pf_server_start_with_peer_socket(proxyServer* server, int peer_fd)
{
struct sockaddr_storage peer_addr;
socklen_t len = sizeof(peer_addr);
freerdp_peer* client = NULL;
WINPR_ASSERT(server);
if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
goto fail;
client = freerdp_peer_new(peer_fd);
if (!client)
goto fail;
if (getpeername(peer_fd, (struct sockaddr*)&peer_addr, &len) != 0)
goto fail;
if (!freerdp_peer_set_local_and_hostname(client, &peer_addr))
goto fail;
client->ContextExtra = server;
if (!pf_server_start_peer(client))
goto fail;
return TRUE;
fail:
WLog_ERR(TAG, "PeerAccepted callback failed");
freerdp_peer_free(client);
return FALSE;
}
static BOOL are_all_required_modules_loaded(proxyModule* module, const proxyConfig* config)
{
size_t i;
for (i = 0; i < pf_config_required_plugins_count(config); i++)
{
const char* plugin_name = pf_config_required_plugin(config, i);
if (!pf_modules_is_plugin_loaded(module, plugin_name))
{
WLog_ERR(TAG, "Required plugin '%s' is not loaded. stopping.", plugin_name);
return FALSE;
}
}
return TRUE;
}
static void peer_free(void* obj)
{
HANDLE hdl = (HANDLE)obj;
CloseHandle(hdl);
}
proxyServer* pf_server_new(const proxyConfig* config)
{
wObject* obj;
proxyServer* server;
WINPR_ASSERT(config);
server = calloc(1, sizeof(proxyServer));
if (!server)
return NULL;
if (!pf_config_clone(&server->config, config))
goto out;
server->module = pf_modules_new(FREERDP_PROXY_PLUGINDIR, pf_config_modules(server->config),
pf_config_modules_count(server->config));
if (!server->module)
{
WLog_ERR(TAG, "failed to initialize proxy modules!");
goto out;
}
pf_modules_list_loaded_plugins(server->module);
if (!are_all_required_modules_loaded(server->module, server->config))
goto out;
server->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!server->stopEvent)
goto out;
server->listener = freerdp_listener_new();
if (!server->listener)
goto out;
server->peer_list = ArrayList_New(FALSE);
if (!server->peer_list)
goto out;
obj = ArrayList_Object(server->peer_list);
WINPR_ASSERT(obj);
obj->fnObjectFree = peer_free;
server->listener->info = server;
server->listener->PeerAccepted = pf_server_peer_accepted;
if (!pf_modules_add(server->module, pf_config_plugin, (void*)server->config))
goto out;
return server;
out:
pf_server_free(server);
return NULL;
}
BOOL pf_server_run(proxyServer* server)
{
BOOL rc = TRUE;
HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
DWORD eventCount;
DWORD status;
freerdp_listener* listener;
WINPR_ASSERT(server);
listener = server->listener;
WINPR_ASSERT(listener);
while (1)
{
WINPR_ASSERT(listener->GetEventHandles);
eventCount = listener->GetEventHandles(listener, eventHandles, ARRAYSIZE(eventHandles));
if ((0 == eventCount) || (eventCount >= ARRAYSIZE(eventHandles)))
{
WLog_ERR(TAG, "Failed to get FreeRDP event handles");
break;
}
WINPR_ASSERT(server->stopEvent);
eventHandles[eventCount++] = server->stopEvent;
status = WaitForMultipleObjects(eventCount, eventHandles, FALSE, 1000);
if (WAIT_FAILED == status)
break;
if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
break;
if (WAIT_FAILED == status)
{
WLog_ERR(TAG, "select failed");
rc = FALSE;
break;
}
WINPR_ASSERT(listener->CheckFileDescriptor);
if (listener->CheckFileDescriptor(listener) != TRUE)
{
WLog_ERR(TAG, "Failed to accept new peer");
// TODO: Set out of resource error
continue;
}
}
WINPR_ASSERT(listener->Close);
listener->Close(listener);
return rc;
}
void pf_server_stop(proxyServer* server)
{
if (!server)
return;
/* signal main thread to stop and wait for the thread to exit */
SetEvent(server->stopEvent);
}
void pf_server_free(proxyServer* server)
{
if (!server)
return;
pf_server_stop(server);
if (server->peer_list)
{
while (ArrayList_Count(server->peer_list) > 0)
{
/* pf_server_stop triggers the threads to shut down.
* loop here until all of them stopped.
*
* This must be done before ArrayList_Free otherwise the thread removal
* in pf_server_handle_peer will deadlock due to both threads trying to
* lock the list.
*/
Sleep(100);
}
}
ArrayList_Free(server->peer_list);
freerdp_listener_free(server->listener);
if (server->stopEvent)
CloseHandle(server->stopEvent);
pf_server_config_free(server->config);
pf_modules_free(server->module);
free(server);
#if defined(WITH_DEBUG_EVENTS)
DumpEventHandles();
#endif
}
BOOL pf_server_add_module(proxyServer* server, proxyModuleEntryPoint ep, void* userdata)
{
WINPR_ASSERT(server);
WINPR_ASSERT(ep);
return pf_modules_add(server->module, ep, userdata);
}