summaryrefslogtreecommitdiffstats
path: root/server/proxy/pf_server.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/proxy/pf_server.c')
-rw-r--r--server/proxy/pf_server.c1073
1 files changed, 1073 insertions, 0 deletions
diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c
new file mode 100644
index 0000000..545ab93
--- /dev/null
+++ b/server/proxy/pf_server.c
@@ -0,0 +1,1073 @@
+/**
+ * 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, rdpSettings* settings,
+ FreeRDP_Settings_Keys_String targetID,
+ FreeRDP_Settings_Keys_UInt32 portID)
+{
+#define TARGET_MAX (100)
+#define ROUTING_TOKEN_PREFIX "Cookie: msts="
+ char* colon = NULL;
+ size_t len = 0;
+ DWORD routing_token_length = 0;
+ 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;
+
+ if (!freerdp_settings_set_string_len(settings, targetID, routing_token + prefix_len, len))
+ return FALSE;
+
+ const char* target = freerdp_settings_get_string(settings, targetID);
+ colon = strchr(target, ':');
+
+ if (colon)
+ {
+ /* port is specified */
+ unsigned long p = strtoul(colon + 1, NULL, 10);
+
+ if (p > USHRT_MAX)
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, portID, p))
+ return FALSE;
+ }
+
+ 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, FreeRDP_ServerHostname, FreeRDP_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_uint32(settings, FreeRDP_TlsSecLevel,
+ config->TargetTlsSecLevel))
+ return FALSE;
+
+ 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);
+ return freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, ev.target_port);
+ }
+ 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 = 0;
+ pServerContext* ps = (pServerContext*)peer->context;
+
+ accepted_channels = WTSGetAcceptedChannelNames(peer, &accepted_channels_count);
+ if (!accepted_channels)
+ return TRUE;
+
+ for (size_t i = 0; i < accepted_channels_count; i++)
+ {
+ pServerStaticChannelContext* channelContext = NULL;
+ 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 = NULL;
+ pClientContext* pc = NULL;
+ rdpSettings* client_settings = NULL;
+ proxyData* pdata = NULL;
+ rdpSettings* frontSettings = NULL;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ frontSettings = peer->context->settings;
+ WINPR_ASSERT(frontSettings);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ const char* ClientHostname = freerdp_settings_get_string(frontSettings, FreeRDP_ClientHostname);
+ PROXY_LOG_INFO(TAG, ps, "Accepted client: %s", 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:%" PRIu32 "",
+ freerdp_settings_get_string(client_settings, FreeRDP_ServerHostname),
+ freerdp_settings_get_uint32(client_settings, FreeRDP_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 = NULL;
+ proxyData* pdata = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ settings = peer->context->settings;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8))
+ return FALSE;
+ 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 = NULL;
+ proxyData* pdata = NULL;
+ 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 = NULL;
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+ const proxyConfig* config = NULL;
+ const pServerStaticChannelContext* channel = NULL;
+ 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)
+{
+ WINPR_ASSERT(peer);
+
+ pServerContext* ps = (pServerContext*)peer->context;
+ if (!ps)
+ return FALSE;
+
+ rdpSettings* settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ proxyData* pdata = proxy_data_new();
+ if (!pdata)
+ return FALSE;
+ proxyServer* server = (proxyServer*)peer->ContextExtra;
+ WINPR_ASSERT(server);
+ proxy_data_set_server_context(pdata, ps);
+
+ pdata->module = server->module;
+ const proxyConfig* config = pdata->config = server->config;
+
+ rdpPrivateKey* key = freerdp_key_new_from_pem(config->PrivateKeyPEM);
+ if (!key)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ return FALSE;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_pem(config->CertificatePEM);
+ if (!cert)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ return FALSE;
+
+ /* currently not supporting GDI orders */
+ {
+ void* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport);
+ ZeroMemory(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 (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;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_CLIENT_COMPATIBLE))
+ return FALSE;
+ 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 };
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ peer_thread_args* args = arg;
+
+ WINPR_ASSERT(args);
+
+ freerdp_peer* client = args->client;
+ WINPR_ASSERT(client);
+
+ proxyServer* server = (proxyServer*)client->ContextExtra;
+ WINPR_ASSERT(server);
+
+ size_t 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);
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_INITIALIZE, pdata, client))
+ goto out_free_peer;
+
+ 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);
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_STARTED, pdata, client))
+ goto out_free_peer;
+
+ while (1)
+ {
+ HANDLE ChannelEvent = INVALID_HANDLE_VALUE;
+ DWORD eventCount = 0;
+ {
+ WINPR_ASSERT(client->GetEventHandles);
+ const DWORD 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;
+
+ const DWORD 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 = NULL;
+ proxyServer* server = NULL;
+ 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)
+{
+ for (size_t 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 = NULL;
+ proxyServer* server = NULL;
+
+ 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:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ pf_server_free(server);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL pf_server_run(proxyServer* server)
+{
+ BOOL rc = TRUE;
+ HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD eventCount = 0;
+ DWORD status = 0;
+ freerdp_listener* listener = NULL;
+
+ 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);
+}