FreeRDP/channels/echo/server/echo_main.c

382 lines
8.7 KiB
C

/**
* 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 <martin.haimberger@thincast.com>
*
* 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 <winpr/assert.h>
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>
#include <freerdp/freerdp.h>
#include <freerdp/server/echo.h>
#include <freerdp/channels/echo.h>
#include <freerdp/channels/log.h>
#define TAG CHANNELS_TAG("echo.server")
typedef struct
{
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 = 0;
HANDLE hEvent = NULL;
DWORD StartTick = 0;
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_DVC_CHANNEL_NAME,
WTS_CHANNEL_OPTION_DYNAMIC);
if (echo->echo_channel)
{
UINT32 channelId = 0;
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 = NULL;
void* buffer = NULL;
DWORD nCount = 0;
HANDLE events[8];
BOOL ready = FALSE;
HANDLE ChannelEvent = NULL;
DWORD BytesReturned = 0;
echo_server* echo = (echo_server*)arg;
UINT error = 0;
DWORD status = 0;
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)
{
union
{
const BYTE* cpv;
CHAR* pv;
} cnv;
cnv.cpv = buffer;
echo_server* echo = (echo_server*)context;
WINPR_ASSERT(echo);
return WTSVirtualChannelWrite(echo->echo_channel, cnv.pv, length, NULL);
}
echo_server_context* echo_server_context_new(HANDLE vcm)
{
echo_server* echo = NULL;
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);
}