diff options
Diffstat (limited to 'server/proxy/pf_server.c')
-rw-r--r-- | server/proxy/pf_server.c | 1073 |
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); +} |