/** * FreeRDP: A Remote Desktop Protocol Implementation * Echo Virtual Channel Extension * * Copyright 2014 Vic Lee * Copyright 2015 Thincast Technologies GmbH * Copyright 2015 DI (FH) Martin Haimberger * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #define TAG CHANNELS_TAG("echo.server") typedef struct _echo_server { echo_server_context context; BOOL opened; HANDLE stopEvent; HANDLE thread; void* echo_channel; DWORD SessionId; } echo_server; /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT echo_server_open_channel(echo_server* echo) { DWORD Error; HANDLE hEvent; DWORD StartTick; DWORD BytesReturned = 0; PULONG pSessionId = NULL; if (WTSQuerySessionInformationA(echo->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, (LPSTR*)&pSessionId, &BytesReturned) == FALSE) { WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); return ERROR_INTERNAL_ERROR; } echo->SessionId = (DWORD)*pSessionId; WTSFreeMemory(pSessionId); hEvent = WTSVirtualChannelManagerGetEventHandle(echo->context.vcm); StartTick = GetTickCount(); while (echo->echo_channel == NULL) { if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) { Error = GetLastError(); WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); return Error; } echo->echo_channel = WTSVirtualChannelOpenEx(echo->SessionId, "ECHO", WTS_CHANNEL_OPTION_DYNAMIC); if (echo->echo_channel) { UINT32 channelId; BOOL status = TRUE; channelId = WTSChannelGetIdByHandle(echo->echo_channel); IFCALLRET(echo->context.ChannelIdAssigned, status, &echo->context, channelId); if (!status) { WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); return ERROR_INTERNAL_ERROR; } break; } Error = GetLastError(); if (Error == ERROR_NOT_FOUND) break; if (GetTickCount() - StartTick > 5000) break; } return echo->echo_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; } static DWORD WINAPI echo_server_thread_func(LPVOID arg) { wStream* s; void* buffer; DWORD nCount; HANDLE events[8]; BOOL ready = FALSE; HANDLE ChannelEvent; DWORD BytesReturned = 0; echo_server* echo = (echo_server*)arg; UINT error; DWORD status; if ((error = echo_server_open_channel(echo))) { UINT error2 = 0; WLog_ERR(TAG, "echo_server_open_channel failed with error %" PRIu32 "!", error); IFCALLRET(echo->context.OpenResult, error2, &echo->context, ECHO_SERVER_OPEN_RESULT_NOTSUPPORTED); if (error2) WLog_ERR(TAG, "echo server's OpenResult callback failed with error %" PRIu32 "", error2); goto out; } buffer = NULL; BytesReturned = 0; ChannelEvent = NULL; if (WTSVirtualChannelQuery(echo->echo_channel, WTSVirtualEventHandle, &buffer, &BytesReturned) == TRUE) { if (BytesReturned == sizeof(HANDLE)) CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); WTSFreeMemory(buffer); } nCount = 0; events[nCount++] = echo->stopEvent; events[nCount++] = ChannelEvent; /* Wait for the client to confirm that the Graphics Pipeline dynamic channel is ready */ while (1) { status = WaitForMultipleObjects(nCount, events, FALSE, 100); if (status == WAIT_FAILED) { error = GetLastError(); WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); break; } if (status == WAIT_OBJECT_0) { IFCALLRET(echo->context.OpenResult, error, &echo->context, ECHO_SERVER_OPEN_RESULT_CLOSED); if (error) WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); break; } if (WTSVirtualChannelQuery(echo->echo_channel, WTSVirtualChannelReady, &buffer, &BytesReturned) == FALSE) { IFCALLRET(echo->context.OpenResult, error, &echo->context, ECHO_SERVER_OPEN_RESULT_ERROR); if (error) WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); break; } ready = *((BOOL*)buffer); WTSFreeMemory(buffer); if (ready) { IFCALLRET(echo->context.OpenResult, error, &echo->context, ECHO_SERVER_OPEN_RESULT_OK); if (error) WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); break; } } s = Stream_New(NULL, 4096); if (!s) { WLog_ERR(TAG, "Stream_New failed!"); WTSVirtualChannelClose(echo->echo_channel); ExitThread(ERROR_NOT_ENOUGH_MEMORY); return ERROR_NOT_ENOUGH_MEMORY; } while (ready) { status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); if (status == WAIT_FAILED) { error = GetLastError(); WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); break; } if (status == WAIT_OBJECT_0) break; Stream_SetPosition(s, 0); WTSVirtualChannelRead(echo->echo_channel, 0, NULL, 0, &BytesReturned); if (BytesReturned < 1) continue; if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) { WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); error = CHANNEL_RC_NO_MEMORY; break; } if (WTSVirtualChannelRead(echo->echo_channel, 0, (PCHAR)Stream_Buffer(s), (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) { WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); error = ERROR_INTERNAL_ERROR; break; } IFCALLRET(echo->context.Response, error, &echo->context, (BYTE*)Stream_Buffer(s), BytesReturned); if (error) { WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); break; } } Stream_Free(s, TRUE); WTSVirtualChannelClose(echo->echo_channel); echo->echo_channel = NULL; out: if (error && echo->context.rdpcontext) setChannelError(echo->context.rdpcontext, error, "echo_server_thread_func reported an error"); ExitThread(error); return error; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT echo_server_open(echo_server_context* context) { echo_server* echo = (echo_server*)context; if (echo->thread == NULL) { if (!(echo->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) { WLog_ERR(TAG, "CreateEvent failed!"); return ERROR_INTERNAL_ERROR; } if (!(echo->thread = CreateThread(NULL, 0, echo_server_thread_func, (void*)echo, 0, NULL))) { WLog_ERR(TAG, "CreateEvent failed!"); CloseHandle(echo->stopEvent); echo->stopEvent = NULL; return ERROR_INTERNAL_ERROR; } } return CHANNEL_RC_OK; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT echo_server_close(echo_server_context* context) { UINT error = CHANNEL_RC_OK; echo_server* echo = (echo_server*)context; if (echo->thread) { SetEvent(echo->stopEvent); if (WaitForSingleObject(echo->thread, INFINITE) == WAIT_FAILED) { error = GetLastError(); WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); return error; } CloseHandle(echo->thread); CloseHandle(echo->stopEvent); echo->thread = NULL; echo->stopEvent = NULL; } return error; } static BOOL echo_server_request(echo_server_context* context, const BYTE* buffer, UINT32 length) { echo_server* echo = (echo_server*)context; return WTSVirtualChannelWrite(echo->echo_channel, (PCHAR)buffer, length, NULL); } echo_server_context* echo_server_context_new(HANDLE vcm) { echo_server* echo; echo = (echo_server*)calloc(1, sizeof(echo_server)); if (echo) { echo->context.vcm = vcm; echo->context.Open = echo_server_open; echo->context.Close = echo_server_close; echo->context.Request = echo_server_request; } else WLog_ERR(TAG, "calloc failed!"); return (echo_server_context*)echo; } void echo_server_context_free(echo_server_context* context) { echo_server* echo = (echo_server*)context; echo_server_close(context); free(echo); }