diff options
Diffstat (limited to 'server')
161 files changed, 39308 insertions, 0 deletions
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt new file mode 100644 index 0000000..895b7a4 --- /dev/null +++ b/server/CMakeLists.txt @@ -0,0 +1,93 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Servers +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + +# Servers +option(WITH_SHADOW "Compile with shadow server" ON) +option(WITH_PROXY "Compile with proxy server" ON) +option(WITH_PLATFORM_SERVER "Compile with platform server" ON) + +add_subdirectory(common) +if (WITH_SHADOW) + add_subdirectory(shadow) +endif() +if (WITH_PROXY) + add_subdirectory(proxy) +endif() + +if(WITH_SAMPLE) + add_subdirectory(Sample) +endif() + +if (WITH_PLATFORM_SERVER) + if(NOT WIN32) + if(APPLE AND (NOT IOS)) + add_subdirectory(Mac) + endif() + else() + add_subdirectory(Windows) + endif() + + if(NOT DEFINED WITH_FREERDS) + set(WITH_FREERDS 1) + endif() + + if(WITH_FREERDS AND (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/FreeRDS")) + add_subdirectory("FreeRDS") + endif() +endif() + +# Pick up other servers + +set(FILENAME "ModuleOptions.cmake") +file(GLOB FILEPATHS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/${FILENAME}") + +foreach(FILEPATH ${FILEPATHS}) + if(${FILEPATH} MATCHES "^([^/]*)/+${FILENAME}") + string(REGEX REPLACE "^([^/]*)/+${FILENAME}" "\\1" FREERDP_SERVER ${FILEPATH}) + set(FREERDP_SERVER_ENABLED 0) + include(${FILEPATH}) + if(FREERDP_SERVER_ENABLED) + if(NOT (${FREERDP_SERVER_VENDOR} MATCHES "FreeRDP")) + list(APPEND FREERDP_EXTRA_SERVERS ${FREERDP_SERVER}) + endif() + endif() + endif() +endforeach() + +foreach(FREERDP_SERVER ${FREERDP_EXTRA_SERVERS}) + add_subdirectory(${FREERDP_SERVER}) +endforeach() + +include(pkg-config-install-prefix) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-server.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp-server${FREERDP_VERSION_MAJOR}.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp-server${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR}) + +export(PACKAGE freerdp-server) + +SetFreeRDPCMakeInstallDir(FREERDP_SERVER_CMAKE_INSTALL_DIR "FreeRDP-Server${FREERDP_VERSION_MAJOR}") + +configure_package_config_file(FreeRDP-ServerConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfig.cmake + INSTALL_DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR} + PATH_VARS FREERDP_INCLUDE_DIR) + +write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfigVersion.cmake + VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ServerConfigVersion.cmake + DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR}) + +install(EXPORT FreeRDP-ServerTargets DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR}) diff --git a/server/FreeRDP-ServerConfig.cmake.in b/server/FreeRDP-ServerConfig.cmake.in new file mode 100644 index 0000000..db7cb44 --- /dev/null +++ b/server/FreeRDP-ServerConfig.cmake.in @@ -0,0 +1,13 @@ +include(CMakeFindDependencyMacro) +find_dependency(WinPR @FREERDP_VERSION@) +find_dependency(FreeRDP @FREERDP_VERSION@) + +@PACKAGE_INIT@ + +set(FreeRDP-Server_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@") +set(FreeRDP-Server_VERSION_MINOR "@FREERDP_VERSION_MINOR@") +set(FreeRDP-Server_VERSION_REVISION "@FREERDP_VERSION_REVISION@") + +set_and_check(FreeRDP-Server_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@") + +include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ServerTargets.cmake") diff --git a/server/Mac/CMakeLists.txt b/server/Mac/CMakeLists.txt new file mode 100644 index 0000000..9dddb38 --- /dev/null +++ b/server/Mac/CMakeLists.txt @@ -0,0 +1,78 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Mac OS X Server cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + +set(MODULE_NAME "mfreerdp-server") +set(MODULE_PREFIX "FREERDP_SERVER_MAC") + +FIND_LIBRARY(AUDIO_TOOL AudioToolbox) +FIND_LIBRARY(CORE_AUDIO CoreAudio) +FIND_LIBRARY(CORE_VIDEO CoreVideo) +FIND_LIBRARY(APP_SERVICES ApplicationServices) +FIND_LIBRARY(IOKIT IOKit) +FIND_LIBRARY(IOSURFACE IOSurface) +FIND_LIBRARY(CARBON Carbon) + +set(${MODULE_PREFIX}_SRCS + mfreerdp.c + mfreerdp.h + mf_interface.c + mf_interface.h + mf_event.c + mf_event.h + mf_peer.c + mf_peer.h + mf_info.c + mf_info.h + mf_input.c + mf_input.h + mf_mountain_lion.c + mf_mountain_lion.h) + +if(CHANNEL_AUDIN_SERVER) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} + mf_audin.c + mf_audin.h) +endif() + +if(CHANNEL_RDPSND_SERVER) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} + mf_rdpsnd.c + mf_rdpsnd.h) + +endif() + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) +if (WITH_BINARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}") +endif() + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} + freerdp-server + ${AUDIO_TOOL} + ${CORE_AUDIO} + ${CORE_VIDEO} + ${APP_SERVICES} + ${IOKIT} + ${IOSURFACE} + ${CARBON}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Mac") diff --git a/server/Mac/ModuleOptions.cmake b/server/Mac/ModuleOptions.cmake new file mode 100644 index 0000000..bfd36cb --- /dev/null +++ b/server/Mac/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_SERVER_NAME "mfreerdp-server") +set(FREERDP_SERVER_PLATFORM "X11") +set(FREERDP_SERVER_VENDOR "FreeRDP") diff --git a/server/Mac/mf_audin.c b/server/Mac/mf_audin.c new file mode 100644 index 0000000..aaabafa --- /dev/null +++ b/server/Mac/mf_audin.c @@ -0,0 +1,64 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server (Audio Input) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de> + * + * 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 "mfreerdp.h" + +#include "mf_audin.h" +#include "mf_interface.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> +#define TAG SERVER_TAG("mac") + +static UINT mf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data) +{ + /* TODO: Implement */ + WINPR_ASSERT(audin); + WINPR_ASSERT(data); + + WLog_WARN(TAG, "not implemented"); + WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data)); + return CHANNEL_RC_OK; +} + +BOOL mf_peer_audin_init(mfPeerContext* context) +{ + WINPR_ASSERT(context); + + context->audin = audin_server_context_new(context->vcm); + context->audin->rdpcontext = &context->_p; + context->audin->userdata = context; + + context->audin->Data = mf_peer_audin_data; + + return audin_server_set_formats(context->audin, -1, NULL); +} + +void mf_peer_audin_uninit(mfPeerContext* context) +{ + WINPR_ASSERT(context); + + audin_server_context_free(context->audin); + context->audin = NULL; +} diff --git a/server/Mac/mf_audin.h b/server/Mac/mf_audin.h new file mode 100644 index 0000000..31edffa --- /dev/null +++ b/server/Mac/mf_audin.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server (Audio Input) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2013 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_AUDIN_H +#define FREERDP_SERVER_MAC_AUDIN_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> + +#include "mf_types.h" +#include "mfreerdp.h" + +BOOL mf_peer_audin_init(mfPeerContext* context); +void mf_peer_audin_uninit(mfPeerContext* context); + +#endif /* FREERDP_SERVER_MAC_AUDIN_H */ diff --git a/server/Mac/mf_event.c b/server/Mac/mf_event.c new file mode 100644 index 0000000..e30b9ac --- /dev/null +++ b/server/Mac/mf_event.c @@ -0,0 +1,219 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * OS X Server Event Handling + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mf_event.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("mac") + +int mf_is_event_set(mfEventQueue* event_queue) +{ + fd_set rfds; + int num_set; + struct timeval time = { 0 }; + + FD_ZERO(&rfds); + FD_SET(event_queue->pipe_fd[0], &rfds); + num_set = select(event_queue->pipe_fd[0] + 1, &rfds, 0, 0, &time); + + return (num_set == 1); +} + +void mf_signal_event(mfEventQueue* event_queue) +{ + int length; + + length = write(event_queue->pipe_fd[1], "sig", 4); + + if (length != 4) + WLog_ERR(TAG, "mf_signal_event: error"); +} + +void mf_set_event(mfEventQueue* event_queue) +{ + int length; + + length = write(event_queue->pipe_fd[1], "sig", 4); + + if (length != 4) + WLog_ERR(TAG, "mf_set_event: error"); +} + +void mf_clear_events(mfEventQueue* event_queue) +{ + int length; + + while (mf_is_event_set(event_queue)) + { + length = read(event_queue->pipe_fd[0], &length, 4); + + if (length != 4) + WLog_ERR(TAG, "mf_clear_event: error"); + } +} + +void mf_clear_event(mfEventQueue* event_queue) +{ + int length; + + length = read(event_queue->pipe_fd[0], &length, 4); + + if (length != 4) + WLog_ERR(TAG, "mf_clear_event: error"); +} + +void mf_event_push(mfEventQueue* event_queue, mfEvent* event) +{ + pthread_mutex_lock(&(event_queue->mutex)); + + if (event_queue->count >= event_queue->size) + { + event_queue->size *= 2; + event_queue->events = + (mfEvent**)realloc((void*)event_queue->events, sizeof(mfEvent*) * event_queue->size); + } + + event_queue->events[(event_queue->count)++] = event; + + pthread_mutex_unlock(&(event_queue->mutex)); + + mf_set_event(event_queue); +} + +mfEvent* mf_event_peek(mfEventQueue* event_queue) +{ + mfEvent* event; + + pthread_mutex_lock(&(event_queue->mutex)); + + if (event_queue->count < 1) + event = NULL; + else + event = event_queue->events[0]; + + pthread_mutex_unlock(&(event_queue->mutex)); + + return event; +} + +mfEvent* mf_event_pop(mfEventQueue* event_queue) +{ + mfEvent* event; + + pthread_mutex_lock(&(event_queue->mutex)); + + if (event_queue->count < 1) + return NULL; + + /* remove event signal */ + mf_clear_event(event_queue); + + event = event_queue->events[0]; + (event_queue->count)--; + + memmove(&event_queue->events[0], &event_queue->events[1], event_queue->count * sizeof(void*)); + + pthread_mutex_unlock(&(event_queue->mutex)); + + return event; +} + +mfEventRegion* mf_event_region_new(int x, int y, int width, int height) +{ + mfEventRegion* event_region = malloc(sizeof(mfEventRegion)); + + if (event_region != NULL) + { + event_region->x = x; + event_region->y = y; + event_region->width = width; + event_region->height = height; + } + + return event_region; +} + +void mf_event_region_free(mfEventRegion* event_region) +{ + free(event_region); +} + +mfEvent* mf_event_new(int type) +{ + mfEvent* event = malloc(sizeof(mfEvent)); + if (!event) + return NULL; + event->type = type; + return event; +} + +void mf_event_free(mfEvent* event) +{ + free(event); +} + +mfEventQueue* mf_event_queue_new() +{ + mfEventQueue* event_queue = malloc(sizeof(mfEventQueue)); + + if (event_queue != NULL) + { + event_queue->pipe_fd[0] = -1; + event_queue->pipe_fd[1] = -1; + + event_queue->size = 16; + event_queue->count = 0; + event_queue->events = (mfEvent**)malloc(sizeof(mfEvent*) * event_queue->size); + + if (pipe(event_queue->pipe_fd) < 0) + { + free(event_queue); + return NULL; + } + + pthread_mutex_init(&(event_queue->mutex), NULL); + } + + return event_queue; +} + +void mf_event_queue_free(mfEventQueue* event_queue) +{ + if (event_queue->pipe_fd[0] != -1) + { + close(event_queue->pipe_fd[0]); + event_queue->pipe_fd[0] = -1; + } + + if (event_queue->pipe_fd[1] != -1) + { + close(event_queue->pipe_fd[1]); + event_queue->pipe_fd[1] = -1; + } + + pthread_mutex_destroy(&(event_queue->mutex)); +} diff --git a/server/Mac/mf_event.h b/server/Mac/mf_event.h new file mode 100644 index 0000000..197cfd7 --- /dev/null +++ b/server/Mac/mf_event.h @@ -0,0 +1,75 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * OS X Server Event Handling + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_EVENT_H +#define FREERDP_SERVER_MAC_EVENT_H + +typedef struct mf_event mfEvent; +typedef struct mf_event_queue mfEventQueue; +typedef struct mf_event_region mfEventRegion; + +#include <pthread.h> +#include "mfreerdp.h" + +//#include "mf_peer.h" + +enum mf_event_type +{ + FREERDP_SERVER_MAC_EVENT_TYPE_REGION, + FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK +}; + +struct mf_event +{ + int type; +}; + +struct mf_event_queue +{ + int size; + int count; + int pipe_fd[2]; + mfEvent** events; + pthread_mutex_t mutex; +}; + +struct mf_event_region +{ + int type; + + int x; + int y; + int width; + int height; +}; + +void mf_event_push(mfEventQueue* event_queue, mfEvent* event); +mfEvent* mf_event_peek(mfEventQueue* event_queue); +mfEvent* mf_event_pop(mfEventQueue* event_queue); + +mfEventRegion* mf_event_region_new(int x, int y, int width, int height); +void mf_event_region_free(mfEventRegion* event_region); + +mfEvent* mf_event_new(int type); +void mf_event_free(mfEvent* event); + +mfEventQueue* mf_event_queue_new(void); +void mf_event_queue_free(mfEventQueue* event_queue); + +#endif /* FREERDP_SERVER_MAC_EVENT_H */ diff --git a/server/Mac/mf_info.c b/server/Mac/mf_info.c new file mode 100644 index 0000000..ab523e1 --- /dev/null +++ b/server/Mac/mf_info.c @@ -0,0 +1,231 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Mac OS X Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <stdlib.h> +#include <errno.h> + +#include "mf_info.h" +#include "mf_mountain_lion.h" + +#define MF_INFO_DEFAULT_FPS 30 +#define MF_INFO_MAXPEERS 32 + +static mfInfo* mfInfoInstance = NULL; + +int mf_info_lock(mfInfo* mfi) +{ + int status = pthread_mutex_lock(&mfi->mutex); + + switch (status) + { + case 0: + return TRUE; + break; + + default: + return -1; + break; + } + + return 1; +} + +int mf_info_try_lock(mfInfo* mfi, UINT32 ms) +{ + int status = pthread_mutex_trylock(&mfi->mutex); + + switch (status) + { + case 0: + return TRUE; + break; + + case EBUSY: + return FALSE; + break; + + default: + return -1; + break; + } + + return 1; +} + +int mf_info_unlock(mfInfo* mfi) +{ + int status = pthread_mutex_unlock(&mfi->mutex); + + switch (status) + { + case 0: + return TRUE; + break; + + default: + return -1; + break; + } + + return 1; +} + +mfInfo* mf_info_init() +{ + mfInfo* mfi; + + mfi = (mfInfo*)calloc(1, sizeof(mfInfo)); + + if (mfi != NULL) + { + pthread_mutex_init(&mfi->mutex, NULL); + + mfi->peers = (freerdp_peer**)calloc(MF_INFO_MAXPEERS, sizeof(freerdp_peer*)); + if (!mfi->peers) + { + free(mfi); + return NULL; + } + + mfi->framesPerSecond = MF_INFO_DEFAULT_FPS; + mfi->input_disabled = FALSE; + } + + return mfi; +} + +mfInfo* mf_info_get_instance() +{ + if (mfInfoInstance == NULL) + mfInfoInstance = mf_info_init(); + + return mfInfoInstance; +} + +void mf_info_peer_register(mfInfo* mfi, mfPeerContext* context) +{ + if (mf_info_lock(mfi) > 0) + { + int peerId; + + if (mfi->peerCount == MF_INFO_MAXPEERS) + { + mf_info_unlock(mfi); + return; + } + + context->info = mfi; + + if (mfi->peerCount == 0) + { + mf_mlion_display_info(&mfi->servscreen_width, &mfi->servscreen_height, &mfi->scale); + mf_mlion_screen_updates_init(); + mf_mlion_start_getting_screen_updates(); + } + + peerId = 0; + + for (int i = 0; i < MF_INFO_MAXPEERS; ++i) + { + // empty index will be our peer id + if (mfi->peers[i] == NULL) + { + peerId = i; + break; + } + } + + mfi->peers[peerId] = ((rdpContext*)context)->peer; + mfi->peers[peerId]->pId = peerId; + mfi->peerCount++; + + mf_info_unlock(mfi); + } +} + +void mf_info_peer_unregister(mfInfo* mfi, mfPeerContext* context) +{ + if (mf_info_lock(mfi) > 0) + { + int peerId; + + peerId = ((rdpContext*)context)->peer->pId; + mfi->peers[peerId] = NULL; + mfi->peerCount--; + + if (mfi->peerCount == 0) + mf_mlion_stop_getting_screen_updates(); + + mf_info_unlock(mfi); + } +} + +BOOL mf_info_have_updates(mfInfo* mfi) +{ + if (mfi->framesWaiting == 0) + return FALSE; + + return TRUE; +} + +void mf_info_update_changes(mfInfo* mfi) +{ +} + +void mf_info_find_invalid_region(mfInfo* mfi) +{ + mf_mlion_get_dirty_region(&mfi->invalid); +} + +void mf_info_clear_invalid_region(mfInfo* mfi) +{ + mf_mlion_clear_dirty_region(); + mfi->invalid.height = 0; + mfi->invalid.width = 0; +} + +void mf_info_invalidate_full_screen(mfInfo* mfi) +{ + mfi->invalid.x = 0; + mfi->invalid.y = 0; + mfi->invalid.height = mfi->servscreen_height; + mfi->invalid.width = mfi->servscreen_width; +} + +BOOL mf_info_have_invalid_region(mfInfo* mfi) +{ + if (mfi->invalid.width * mfi->invalid.height == 0) + return FALSE; + + return TRUE; +} + +void mf_info_getScreenData(mfInfo* mfi, long* width, long* height, BYTE** pBits, int* pitch) +{ + *width = mfi->invalid.width / mfi->scale; + *height = mfi->invalid.height / mfi->scale; + *pitch = mfi->servscreen_width * mfi->scale * 4; + + mf_mlion_get_pixelData(mfi->invalid.x / mfi->scale, mfi->invalid.y / mfi->scale, *width, + *height, pBits); + + *pBits = *pBits + (mfi->invalid.x * 4) + (*pitch * mfi->invalid.y); +} diff --git a/server/Mac/mf_info.h b/server/Mac/mf_info.h new file mode 100644 index 0000000..8f08982 --- /dev/null +++ b/server/Mac/mf_info.h @@ -0,0 +1,49 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_INFO_H +#define FREERDP_SERVER_MAC_INFO_H + +#define FREERDP_SERVER_MAC_INFO_DEFAULT_FPS 1 +#define FREERDP_SERVER_MAC_INFO_MAXPEERS 1 + +#include <winpr/wtypes.h> +#include <freerdp/codec/rfx.h> + +#include "mf_interface.h" + +int mf_info_lock(mfInfo* mfi); +int mf_info_try_lock(mfInfo* mfi, UINT32 ms); +int mf_info_unlock(mfInfo* mfi); + +mfInfo* mf_info_get_instance(void); +void mf_info_peer_register(mfInfo* mfi, mfPeerContext* context); +void mf_info_peer_unregister(mfInfo* mfi, mfPeerContext* context); + +BOOL mf_info_have_updates(mfInfo* mfi); +void mf_info_update_changes(mfInfo* mfi); +void mf_info_find_invalid_region(mfInfo* mfi); +void mf_info_clear_invalid_region(mfInfo* mfi); +void mf_info_invalidate_full_screen(mfInfo* mfi); +BOOL mf_info_have_invalid_region(mfInfo* mfi); +void mf_info_getScreenData(mfInfo* mfi, long* width, long* height, BYTE** pBits, int* pitch); +// BOOL CALLBACK mf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM +// dwData); + +#endif /* FREERDP_SERVER_MAC_INFO_H */ diff --git a/server/Mac/mf_input.c b/server/Mac/mf_input.c new file mode 100644 index 0000000..fd4af85 --- /dev/null +++ b/server/Mac/mf_input.c @@ -0,0 +1,511 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server (Input) + * + * Copyright 2013 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <ApplicationServices/ApplicationServices.h> +#include <Carbon/Carbon.h> + +#include <winpr/windows.h> + +#include "mf_input.h" +#include "mf_info.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("mac") + +static const CGKeyCode keymap[256] = { + 0xFF, // 0x0 + kVK_Escape, // 0x1 + kVK_ANSI_1, // 0x2 + kVK_ANSI_2, // 0x3 + kVK_ANSI_3, // 0x4 + kVK_ANSI_4, // 0x5 + kVK_ANSI_5, // 0x6 + kVK_ANSI_6, // 0x7 + kVK_ANSI_7, // 0x8 + kVK_ANSI_8, // 0x9 + kVK_ANSI_9, // 0xa + kVK_ANSI_0, // 0xb + kVK_ANSI_Minus, // 0xc + kVK_ANSI_Equal, // 0xd + kVK_Delete, // 0xe + kVK_Tab, // 0xf + kVK_ANSI_Q, // 0x10 + kVK_ANSI_W, // 0x11 + kVK_ANSI_E, // 0x12 + kVK_ANSI_R, // 0x13 + kVK_ANSI_T, // 0x14 + kVK_ANSI_Y, // 0x15 + kVK_ANSI_U, // 0x16 + kVK_ANSI_I, // 0x17 + kVK_ANSI_O, // 0x18 + kVK_ANSI_P, // 0x19 + kVK_ANSI_LeftBracket, // 0x1a + kVK_ANSI_RightBracket, // 0x1b + kVK_Return, // 0x1c + kVK_Control, // 0x1d + kVK_ANSI_A, // 0x1e + kVK_ANSI_S, // 0x1f + kVK_ANSI_D, // 0x20 + kVK_ANSI_F, // 0x21 + kVK_ANSI_G, // 0x22 + kVK_ANSI_H, // 0x23 + kVK_ANSI_J, // 0x24 + kVK_ANSI_K, // 0x25 + kVK_ANSI_L, // 0x26 + kVK_ANSI_Semicolon, // 0x27 + kVK_ANSI_Quote, // 0x28 + kVK_ANSI_Grave, // 0x29 + kVK_Shift, // 0x2a + kVK_ANSI_Backslash, // 0x2b + kVK_ANSI_Z, // 0x2c + kVK_ANSI_X, // 0x2d + kVK_ANSI_C, // 0x2e + kVK_ANSI_V, // 0x2f + kVK_ANSI_B, // 0x30 + kVK_ANSI_N, // 0x31 + kVK_ANSI_M, // 0x32 + kVK_ANSI_Comma, // 0x33 + kVK_ANSI_Period, // 0x34 + kVK_ANSI_Slash, // 0x35 + kVK_Shift, // 0x36 + kVK_ANSI_KeypadMultiply, // 0x37 + kVK_Option, // 0x38 + kVK_Space, // 0x39 + kVK_CapsLock, // 0x3a + kVK_F1, // 0x3b + kVK_F2, // 0x3c + kVK_F3, // 0x3d + kVK_F4, // 0x3e + kVK_F5, // 0x3f + kVK_F6, // 0x40 + kVK_F7, // 0x41 + kVK_F8, // 0x42 + kVK_F9, // 0x43 + kVK_F10, // 0x44 + 0xFF, // 0x45 -- numlock + 0xFF, // 0x46 -- scroll lock + kVK_ANSI_Keypad7, // 0x47 + kVK_ANSI_Keypad8, // 0x48 + kVK_ANSI_Keypad9, // 0x49 + kVK_ANSI_KeypadMinus, // 0x4a + kVK_ANSI_Keypad4, // 0x4b + kVK_ANSI_Keypad5, // 0x4c + kVK_ANSI_Keypad6, // 0x4d + kVK_ANSI_KeypadPlus, // 0x4e + kVK_ANSI_Keypad1, // 0x4f + kVK_ANSI_Keypad2, // 0x50 + kVK_ANSI_Keypad3, // 0x51 + kVK_ANSI_Keypad0, // 0x52 + kVK_ANSI_KeypadDecimal, // 0x53 + 0xFF, // 0x54 + 0xFF, // 0x55 + 0xFF, // 0x56 + kVK_F11, // 0x57 + kVK_F12, // 0x58 + 0xFF, // 0x59 -- pause + 0xFF, // 0x5a + kVK_Control, // 0x5b + kVK_Control, // 0x5c + 0xFF, // 0x5d -- application + 0xFF, // 0x5e -- power + 0xFF, // 0x5f -- sleep + 0xFF, // 0x60 + 0xFF, // 0x61 + 0xFF, // 0x62 + 0xFF, // 0x63 -- wake + 0xFF, // 0x64 + 0xFF, // 0x65 + 0xFF, // 0x66 + 0xFF, // 0x67 + 0xFF, // 0x68 + 0xFF, // 0x69 + 0xFF, // 0x6a + 0xFF, // 0x6b + 0xFF, // 0x6c + 0xFF, // 0x6d + 0xFF, // 0x6e + 0xFF, // 0x6f + 0xFF, // 0x70 + 0xFF, // 0x71 + 0xFF, // 0x72 + 0xFF, // 0x73 + 0xFF, // 0x74 + 0xFF, // 0x75 + 0xFF, // 0x76 + 0xFF, // 0x77 + 0xFF, // 0x78 + 0xFF, // 0x79 + 0xFF, // 0x7a + 0xFF, // 0x7b + 0xFF, // 0x7c + 0xFF, // 0x7d + 0xFF, // 0x7e + 0xFF, // 0x7f + 0xFF, // 0x80 + 0xFF, // 0x81 + 0xFF, // 0x82 + 0xFF, // 0x83 + 0xFF, // 0x84 + 0xFF, // 0x85 + 0xFF, // 0x86 + 0xFF, // 0x87 + 0xFF, // 0x88 + 0xFF, // 0x89 + 0xFF, // 0x8a + 0xFF, // 0x8b + 0xFF, // 0x8c + 0xFF, // 0x8d + 0xFF, // 0x8e + 0xFF, // 0x8f + 0xFF, // 0x90 + 0xFF, // 0x91 + 0xFF, // 0x92 + 0xFF, // 0x93 + 0xFF, // 0x94 + 0xFF, // 0x95 + 0xFF, // 0x96 + 0xFF, // 0x97 + 0xFF, // 0x98 + 0xFF, // 0x99 + 0xFF, // 0x9a + 0xFF, // 0x9b + 0xFF, // 0x9c + 0xFF, // 0x9d + 0xFF, // 0x9e + 0xFF, // 0x9f + 0xFF, // 0xa0 + 0xFF, // 0xa1 + 0xFF, // 0xa2 + 0xFF, // 0xa3 + 0xFF, // 0xa4 + 0xFF, // 0xa5 + 0xFF, // 0xa6 + 0xFF, // 0xa7 + 0xFF, // 0xa8 + 0xFF, // 0xa9 + 0xFF, // 0xaa + 0xFF, // 0xab + 0xFF, // 0xac + 0xFF, // 0xad + 0xFF, // 0xae + 0xFF, // 0xaf + 0xFF, // 0xb0 + 0xFF, // 0xb1 + 0xFF, // 0xb2 + 0xFF, // 0xb3 + 0xFF, // 0xb4 + 0xFF, // 0xb5 + 0xFF, // 0xb6 + 0xFF, // 0xb7 + 0xFF, // 0xb8 + 0xFF, // 0xb9 + 0xFF, // 0xba + 0xFF, // 0xbb + 0xFF, // 0xbc + 0xFF, // 0xbd + 0xFF, // 0xbe + 0xFF, // 0xbf + 0xFF, // 0xc0 + 0xFF, // 0xc1 + 0xFF, // 0xc2 + 0xFF, // 0xc3 + 0xFF, // 0xc4 + 0xFF, // 0xc5 + 0xFF, // 0xc6 + 0xFF, // 0xc7 + 0xFF, // 0xc8 + 0xFF, // 0xc9 + 0xFF, // 0xca + 0xFF, // 0xcb + 0xFF, // 0xcc + 0xFF, // 0xcd + 0xFF, // 0xce + 0xFF, // 0xcf + 0xFF, // 0xd0 + 0xFF, // 0xd1 + 0xFF, // 0xd2 + 0xFF, // 0xd3 + 0xFF, // 0xd4 + 0xFF, // 0xd5 + 0xFF, // 0xd6 + 0xFF, // 0xd7 + 0xFF, // 0xd8 + 0xFF, // 0xd9 + 0xFF, // 0xda + 0xFF, // 0xdb + 0xFF, // 0xdc + 0xFF, // 0xdd + 0xFF, // 0xde + 0xFF, // 0xdf + 0xFF, // 0xe0 + 0xFF, // 0xe1 + 0xFF, // 0xe2 + 0xFF, // 0xe3 + 0xFF, // 0xe4 + 0xFF, // 0xe5 + 0xFF, // 0xe6 + 0xFF, // 0xe7 + 0xFF, // 0xe8 + 0xFF, // 0xe9 + 0xFF, // 0xea + 0xFF, // 0xeb + 0xFF, // 0xec + 0xFF, // 0xed + 0xFF, // 0xee + 0xFF, // 0xef + 0xFF, // 0xf0 + 0xFF, // 0xf1 + 0xFF, // 0xf2 + 0xFF, // 0xf3 + 0xFF, // 0xf4 + 0xFF, // 0xf5 + 0xFF, // 0xf6 + 0xFF, // 0xf7 + 0xFF, // 0xf8 + 0xFF, // 0xf9 + 0xFF, // 0xfa + 0xFF, // 0xfb + 0xFF, // 0xfc + 0xFF, // 0xfd + 0xFF, // 0xfe +}; + +BOOL mf_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code) +{ + CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + BOOL keyDown = TRUE; + CGEventRef kbEvent; + CGKeyCode kCode = 0xFF; + + if (flags & KBD_FLAGS_RELEASE) + { + keyDown = FALSE; + } + + if (flags & KBD_FLAGS_EXTENDED) + { + switch (code) + { + // case 0x52: //insert + case 0x53: + kCode = kVK_ForwardDelete; + break; + + case 0x4B: + kCode = kVK_LeftArrow; + break; + + case 0x47: + kCode = kVK_Home; + break; + + case 0x4F: + kCode = kVK_End; + break; + + case 0x48: + kCode = kVK_UpArrow; + break; + + case 0x50: + kCode = kVK_DownArrow; + break; + + case 0x49: + kCode = kVK_PageUp; + break; + + case 0x51: + kCode = kVK_PageDown; + break; + + case 0x4D: + kCode = kVK_RightArrow; + break; + + default: + break; + } + } + else + { + kCode = keymap[code]; + } + + kbEvent = CGEventCreateKeyboardEvent(source, kCode, keyDown); + CGEventPost(kCGHIDEventTap, kbEvent); + CFRelease(kbEvent); + CFRelease(source); + return TRUE; +} + +BOOL mf_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) +{ + return FALSE; +} + +BOOL mf_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + float width, height; + CGWheelCount wheelCount = 2; + INT32 scroll_x = 0; + INT32 scroll_y = 0; + + if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL)) + { + INT32 scroll = flags & WheelRotationMask; + + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + scroll = -(flags & WheelRotationMask) / 392; + else + scroll = (flags & WheelRotationMask) / 120; + + if (flags & PTR_FLAGS_WHEEL) + scroll_y = scroll; + else + scroll_x = scroll; + + CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(source, kCGScrollEventUnitLine, + wheelCount, scroll_y, scroll_x); + CGEventPost(kCGHIDEventTap, scrollEvent); + CFRelease(scrollEvent); + CFRelease(source); + } + else + { + mfInfo* mfi; + CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventType mouseType = kCGEventNull; + CGMouseButton mouseButton = kCGMouseButtonLeft; + mfi = mf_info_get_instance(); + // width and height of primary screen (even in multimon setups + width = (float)mfi->servscreen_width; + height = (float)mfi->servscreen_height; + x += mfi->servscreen_xoffset; + y += mfi->servscreen_yoffset; + + if (flags & PTR_FLAGS_MOVE) + { + if (mfi->mouse_down_left == TRUE) + { + mouseType = kCGEventLeftMouseDragged; + } + else if (mfi->mouse_down_right == TRUE) + { + mouseType = kCGEventRightMouseDragged; + } + else if (mfi->mouse_down_other == TRUE) + { + mouseType = kCGEventOtherMouseDragged; + } + else + { + mouseType = kCGEventMouseMoved; + } + + CGEventRef move = CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), + mouseButton // ignored for just movement + ); + CGEventPost(kCGHIDEventTap, move); + CFRelease(move); + } + + if (flags & PTR_FLAGS_BUTTON1) + { + mouseButton = kCGMouseButtonLeft; + + if (flags & PTR_FLAGS_DOWN) + { + mouseType = kCGEventLeftMouseDown; + mfi->mouse_down_left = TRUE; + } + else + { + mouseType = kCGEventLeftMouseUp; + mfi->mouse_down_right = FALSE; + } + } + else if (flags & PTR_FLAGS_BUTTON2) + { + mouseButton = kCGMouseButtonRight; + + if (flags & PTR_FLAGS_DOWN) + { + mouseType = kCGEventRightMouseDown; + mfi->mouse_down_right = TRUE; + } + else + { + mouseType = kCGEventRightMouseUp; + mfi->mouse_down_right = FALSE; + } + } + else if (flags & PTR_FLAGS_BUTTON3) + { + mouseButton = kCGMouseButtonCenter; + + if (flags & PTR_FLAGS_DOWN) + { + mouseType = kCGEventOtherMouseDown; + mfi->mouse_down_other = TRUE; + } + else + { + mouseType = kCGEventOtherMouseUp; + mfi->mouse_down_other = FALSE; + } + } + + CGEventRef mouseEvent = + CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton); + CGEventPost(kCGHIDEventTap, mouseEvent); + CFRelease(mouseEvent); + CFRelease(source); + } + + return TRUE; +} + +BOOL mf_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + return FALSE; +} + +BOOL mf_input_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code) +{ + return FALSE; +} + +BOOL mf_input_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code) +{ + return FALSE; +} + +BOOL mf_input_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + return FALSE; +} + +BOOL mf_input_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + return FALSE; +} diff --git a/server/Mac/mf_input.h b/server/Mac/mf_input.h new file mode 100644 index 0000000..4c039f8 --- /dev/null +++ b/server/Mac/mf_input.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server (Input) + * + * Copyright 2013 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_INPUT_H +#define FREERDP_SERVER_MAC_INPUT_H + +#include "mf_interface.h" + +BOOL mf_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code); +BOOL mf_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code); +BOOL mf_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); +BOOL mf_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); + +// dummy versions +BOOL mf_input_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code); +BOOL mf_input_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code); +BOOL mf_input_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); +BOOL mf_input_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); + +#endif /* FREERDP_SERVER_MAC_INPUT_H */ diff --git a/server/Mac/mf_interface.c b/server/Mac/mf_interface.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/server/Mac/mf_interface.c diff --git a/server/Mac/mf_interface.h b/server/Mac/mf_interface.h new file mode 100644 index 0000000..6ba86f3 --- /dev/null +++ b/server/Mac/mf_interface.h @@ -0,0 +1,106 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Mac OS X Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_INTERFACE_H +#define FREERDP_SERVER_MAC_INTERFACE_H + +#include <pthread.h> + +#include <freerdp/config.h> +#include <freerdp/codec/rfx.h> +#include <freerdp/codec/nsc.h> +#include <freerdp/listener.h> +#include <freerdp/freerdp.h> + +#include <winpr/crt.h> + +#ifdef WITH_SERVER_CHANNELS +#include <freerdp/channels/wtsvc.h> +#endif + +#ifdef CHANNEL_RDPSND_SERVER +#include <freerdp/server/rdpsnd.h> +#include "mf_rdpsnd.h" +#endif + +#ifdef CHANNEL_AUDIN_SERVER +#include <freerdp/server/audin.h> +#include "mf_audin.h" +#endif + +#include "mf_types.h" + +struct mf_peer_context +{ + rdpContext _p; + + mfInfo* info; + wStream* s; + BOOL activated; + UINT32 frame_id; + BOOL audin_open; + RFX_CONTEXT* rfx_context; + NSC_CONTEXT* nsc_context; + +#ifdef WITH_SERVER_CHANNELS + HANDLE vcm; +#endif + +#ifdef CHANNEL_AUDIN_SERVER + audin_server_context* audin; +#endif + +#ifdef CHANNEL_RDPSND_SERVER + RdpsndServerContext* rdpsnd; +#endif +}; + +struct mf_info +{ + // STREAM* s; + + // screen and monitor info + UINT32 screenID; + UINT32 virtscreen_width; + UINT32 virtscreen_height; + UINT32 servscreen_width; + UINT32 servscreen_height; + UINT32 servscreen_xoffset; + UINT32 servscreen_yoffset; + + int bitsPerPixel; + int peerCount; + int activePeerCount; + int framesPerSecond; + freerdp_peer** peers; + unsigned int framesWaiting; + UINT32 scale; + + RFX_RECT invalid; + pthread_mutex_t mutex; + + BOOL mouse_down_left; + BOOL mouse_down_right; + BOOL mouse_down_other; + BOOL input_disabled; + BOOL force_all_disconnect; +}; + +#endif /* FREERDP_SERVER_MAC_INTERFACE_H */ diff --git a/server/Mac/mf_mountain_lion.c b/server/Mac/mf_mountain_lion.c new file mode 100644 index 0000000..9db86b8 --- /dev/null +++ b/server/Mac/mf_mountain_lion.c @@ -0,0 +1,269 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * OS X Server Event Handling + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <dispatch/dispatch.h> +#include <CoreGraphics/CoreGraphics.h> +#include <CoreVideo/CoreVideo.h> +#include <IOKit/IOKitLib.h> +#include <IOSurface/IOSurface.h> + +#include "mf_mountain_lion.h" + +dispatch_semaphore_t region_sem; +dispatch_semaphore_t data_sem; +dispatch_queue_t screen_update_q; +CGDisplayStreamRef stream; + +CGDisplayStreamUpdateRef lastUpdate = NULL; + +BYTE* localBuf = NULL; + +BOOL ready = FALSE; + +void (^streamHandler)(CGDisplayStreamFrameStatus, uint64_t, IOSurfaceRef, + CGDisplayStreamUpdateRef) = ^(CGDisplayStreamFrameStatus status, + uint64_t displayTime, IOSurfaceRef frameSurface, + CGDisplayStreamUpdateRef updateRef) { + dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER); + + // may need to move this down + if (ready == TRUE) + { + + RFX_RECT rect; + unsigned long offset_beg; + unsigned long stride; + + rect.x = 0; + rect.y = 0; + rect.width = 0; + rect.height = 0; + mf_mlion_peek_dirty_region(&rect); + + // lock surface + IOSurfaceLock(frameSurface, kIOSurfaceLockReadOnly, NULL); + // get pointer + void* baseAddress = IOSurfaceGetBaseAddress(frameSurface); + // copy region + + stride = IOSurfaceGetBytesPerRow(frameSurface); + // memcpy(localBuf, baseAddress + offset_beg, surflen); + for (int i = 0; i < rect.height; i++) + { + offset_beg = (stride * (rect.y + i) + (rect.x * 4)); + memcpy(localBuf + offset_beg, baseAddress + offset_beg, rect.width * 4); + } + + // unlock surface + IOSurfaceUnlock(frameSurface, kIOSurfaceLockReadOnly, NULL); + + ready = FALSE; + dispatch_semaphore_signal(data_sem); + } + + if (status != kCGDisplayStreamFrameStatusFrameComplete) + { + switch (status) + { + case kCGDisplayStreamFrameStatusFrameIdle: + break; + + case kCGDisplayStreamFrameStatusStopped: + break; + + case kCGDisplayStreamFrameStatusFrameBlank: + break; + + default: + break; + } + } + else if (lastUpdate == NULL) + { + CFRetain(updateRef); + lastUpdate = updateRef; + } + else + { + CGDisplayStreamUpdateRef tmpRef; + tmpRef = lastUpdate; + lastUpdate = CGDisplayStreamUpdateCreateMergedUpdate(tmpRef, updateRef); + CFRelease(tmpRef); + } + + dispatch_semaphore_signal(region_sem); +}; + +int mf_mlion_display_info(UINT32* disp_width, UINT32* disp_height, UINT32* scale) +{ + CGDirectDisplayID display_id; + + display_id = CGMainDisplayID(); + + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id); + + size_t pixelWidth = CGDisplayModeGetPixelWidth(mode); + // size_t pixelHeight = CGDisplayModeGetPixelHeight(mode); + + size_t wide = CGDisplayPixelsWide(display_id); + size_t high = CGDisplayPixelsHigh(display_id); + + CGDisplayModeRelease(mode); + + *disp_width = wide; // pixelWidth; + *disp_height = high; // pixelHeight; + *scale = pixelWidth / wide; + + return 0; +} + +int mf_mlion_screen_updates_init() +{ + CGDirectDisplayID display_id; + + display_id = CGMainDisplayID(); + + screen_update_q = dispatch_queue_create("mfreerdp.server.screenUpdate", NULL); + + region_sem = dispatch_semaphore_create(1); + data_sem = dispatch_semaphore_create(1); + + UINT32 pixelWidth; + UINT32 pixelHeight; + UINT32 scale; + + mf_mlion_display_info(&pixelWidth, &pixelHeight, &scale); + + localBuf = malloc(pixelWidth * pixelHeight * 4); + if (!localBuf) + return -1; + + CFDictionaryRef opts; + + void* keys[2]; + void* values[2]; + + keys[0] = (void*)kCGDisplayStreamShowCursor; + values[0] = (void*)kCFBooleanFalse; + + opts = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1, + NULL, NULL); + + stream = CGDisplayStreamCreateWithDispatchQueue(display_id, pixelWidth, pixelHeight, 'BGRA', + opts, screen_update_q, streamHandler); + + CFRelease(opts); + + return 0; +} + +int mf_mlion_start_getting_screen_updates() +{ + CGError err; + + err = CGDisplayStreamStart(stream); + + if (err != kCGErrorSuccess) + { + return 1; + } + + return 0; +} +int mf_mlion_stop_getting_screen_updates() +{ + CGError err; + + err = CGDisplayStreamStop(stream); + + if (err != kCGErrorSuccess) + { + return 1; + } + + return 0; +} + +int mf_mlion_get_dirty_region(RFX_RECT* invalid) +{ + dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER); + + if (lastUpdate != NULL) + { + mf_mlion_peek_dirty_region(invalid); + } + + dispatch_semaphore_signal(region_sem); + + return 0; +} + +int mf_mlion_peek_dirty_region(RFX_RECT* invalid) +{ + size_t num_rects; + CGRect dirtyRegion; + + const CGRect* rects = + CGDisplayStreamUpdateGetRects(lastUpdate, kCGDisplayStreamUpdateDirtyRects, &num_rects); + + if (num_rects == 0) + { + return 0; + } + + dirtyRegion = *rects; + for (size_t i = 0; i < num_rects; i++) + { + dirtyRegion = CGRectUnion(dirtyRegion, *(rects + i)); + } + + invalid->x = dirtyRegion.origin.x; + invalid->y = dirtyRegion.origin.y; + invalid->height = dirtyRegion.size.height; + invalid->width = dirtyRegion.size.width; + + return 0; +} + +int mf_mlion_clear_dirty_region() +{ + dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER); + + CFRelease(lastUpdate); + lastUpdate = NULL; + + dispatch_semaphore_signal(region_sem); + + return 0; +} + +int mf_mlion_get_pixelData(long x, long y, long width, long height, BYTE** pxData) +{ + dispatch_semaphore_wait(region_sem, DISPATCH_TIME_FOREVER); + ready = TRUE; + dispatch_semaphore_wait(data_sem, DISPATCH_TIME_FOREVER); + dispatch_semaphore_signal(region_sem); + + // this second wait allows us to block until data is copied... more on this later + dispatch_semaphore_wait(data_sem, DISPATCH_TIME_FOREVER); + *pxData = localBuf; + dispatch_semaphore_signal(data_sem); + + return 0; +} diff --git a/server/Mac/mf_mountain_lion.h b/server/Mac/mf_mountain_lion.h new file mode 100644 index 0000000..c1fbe9b --- /dev/null +++ b/server/Mac/mf_mountain_lion.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * OS X Server Event Handling + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_MLION_H +#define FREERDP_SERVER_MAC_MLION_H + +#include <freerdp/codec/rfx.h> + +int mf_mlion_display_info(UINT32* disp_width, UINT32* dispHeight, UINT32* scale); + +int mf_mlion_screen_updates_init(void); + +int mf_mlion_start_getting_screen_updates(void); +int mf_mlion_stop_getting_screen_updates(void); + +int mf_mlion_get_dirty_region(RFX_RECT* invalid); +int mf_mlion_peek_dirty_region(RFX_RECT* invalid); +int mf_mlion_clear_dirty_region(void); + +int mf_mlion_get_pixelData(long x, long y, long width, long height, BYTE** pxData); + +#endif /* FREERDP_SERVER_MAC_MLION_H */ diff --git a/server/Mac/mf_peer.c b/server/Mac/mf_peer.c new file mode 100644 index 0000000..0669127 --- /dev/null +++ b/server/Mac/mf_peer.c @@ -0,0 +1,485 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Mac OS X Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <freerdp/listener.h> +#include <freerdp/codec/rfx.h> +#include <winpr/stream.h> +#include <freerdp/peer.h> +#include <freerdp/codec/color.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> + +#include "mf_peer.h" +#include "mf_info.h" +#include "mf_input.h" +#include "mf_event.h" +#include "mf_rdpsnd.h" +#include "mf_audin.h" + +#include <mach/clock.h> +#include <mach/mach.h> +#include <dispatch/dispatch.h> + +#include "OpenGL/OpenGL.h" +#include "OpenGL/gl.h" + +#include "CoreVideo/CoreVideo.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("mac") + +// refactor these +static int info_last_sec = 0; +static int info_last_nsec = 0; + +static dispatch_source_t info_timer; +static dispatch_queue_t info_queue; + +static mfEventQueue* info_event_queue; + +static CGLContextObj glContext; +static CGContextRef bmp; +static CGImageRef img; + +static void mf_peer_context_free(freerdp_peer* client, rdpContext* context); + +static BOOL mf_peer_get_fds(freerdp_peer* client, void** rfds, int* rcount) +{ + if (info_event_queue->pipe_fd[0] == -1) + return TRUE; + + rfds[*rcount] = (void*)(long)info_event_queue->pipe_fd[0]; + (*rcount)++; + return TRUE; +} + +static void mf_peer_rfx_update(freerdp_peer* client) +{ + // check + mfInfo* mfi = mf_info_get_instance(); + mf_info_find_invalid_region(mfi); + + if (mf_info_have_invalid_region(mfi) == false) + { + return; + } + + long width; + long height; + int pitch; + BYTE* dataBits = NULL; + mf_info_getScreenData(mfi, &width, &height, &dataBits, &pitch); + mf_info_clear_invalid_region(mfi); + // encode + wStream* s; + RFX_RECT rect; + rdpUpdate* update; + mfPeerContext* mfp; + SURFACE_BITS_COMMAND cmd = { 0 }; + + WINPR_ASSERT(client); + + mfp = (mfPeerContext*)client->context; + WINPR_ASSERT(mfp); + + update = client->context->update; + WINPR_ASSERT(update); + + s = mfp->s; + WINPR_ASSERT(s); + + Stream_Clear(s); + Stream_SetPosition(s, 0); + UINT32 x = mfi->invalid.x / mfi->scale; + UINT32 y = mfi->invalid.y / mfi->scale; + rect.x = 0; + rect.y = 0; + rect.width = width; + rect.height = height; + + rfx_context_reset(mfp->rfx_context, mfi->servscreen_width, mfi->servscreen_height); + + if (!(rfx_compose_message(mfp->rfx_context, s, &rect, 1, (BYTE*)dataBits, rect.width, + rect.height, pitch))) + { + return; + } + + cmd.destLeft = x; + cmd.destTop = y; + cmd.destRight = x + rect.width; + cmd.destBottom = y + rect.height; + cmd.bmp.bpp = 32; + cmd.bmp.codecID = 3; + cmd.bmp.width = rect.width; + cmd.bmp.height = rect.height; + cmd.bmp.bitmapDataLength = Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + // send + update->SurfaceBits(update->context, &cmd); + // clean up... maybe? +} + +static BOOL mf_peer_check_fds(freerdp_peer* client) +{ + mfPeerContext* context = (mfPeerContext*)client->context; + mfEvent* event; + + if (context->activated == FALSE) + return TRUE; + + event = mf_event_peek(info_event_queue); + + if (event != NULL) + { + if (event->type == FREERDP_SERVER_MAC_EVENT_TYPE_REGION) + { + } + else if (event->type == FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK) + { + event = mf_event_pop(info_event_queue); + mf_peer_rfx_update(client); + mf_event_free(event); + } + } + + return TRUE; +} + +/* Called when we have a new peer connecting */ +static BOOL mf_peer_context_new(freerdp_peer* client, rdpContext* context) +{ + rdpSettings* settings; + mfPeerContext* peer = (mfPeerContext*)context; + + WINPR_ASSERT(client); + WINPR_ASSERT(context); + + settings = context->settings; + WINPR_ASSERT(settings); + + if (!(peer->info = mf_info_get_instance())) + return FALSE; + + if (!(peer->rfx_context = rfx_context_new_ex( + TRUE, freerdp_settings_get_uint32(settings, FreeRDP_ThreadingFlags)))) + goto fail; + + rfx_context_reset(peer->rfx_context, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + rfx_context_set_mode(peer->rfx_context, RLGR3); + rfx_context_set_pixel_format(peer->rfx_context, PIXEL_FORMAT_BGRA32); + + if (!(peer->s = Stream_New(NULL, 0xFFFF))) + goto fail; + + peer->vcm = WTSOpenServerA((LPSTR)client->context); + + if (!peer->vcm || (peer->vcm == INVALID_HANDLE_VALUE)) + goto fail; + + mf_info_peer_register(peer->info, peer); + return TRUE; +fail: + mf_peer_context_free(client, context); + return FALSE; +} + +/* Called after a peer disconnects */ +static void mf_peer_context_free(freerdp_peer* client, rdpContext* context) +{ + mfPeerContext* peer = (mfPeerContext*)context; + if (context) + { + mf_info_peer_unregister(peer->info, peer); + dispatch_suspend(info_timer); + Stream_Free(peer->s, TRUE); + rfx_context_free(peer->rfx_context); + // nsc_context_free(peer->nsc_context); +#ifdef CHANNEL_AUDIN_SERVER + + mf_peer_audin_uninit(peer); + +#endif +#ifdef CHANNEL_RDPSND_SERVER + mf_peer_rdpsnd_stop(); + + if (peer->rdpsnd) + rdpsnd_server_context_free(peer->rdpsnd); + +#endif + WTSCloseServer(peer->vcm); + } +} + +/* Called when a new client connects */ +static BOOL mf_peer_init(freerdp_peer* client) +{ + client->ContextSize = sizeof(mfPeerContext); + client->ContextNew = mf_peer_context_new; + client->ContextFree = mf_peer_context_free; + + if (!freerdp_peer_context_new(client)) + return FALSE; + + info_event_queue = mf_event_queue_new(); + info_queue = dispatch_queue_create("FreeRDP.update.timer", DISPATCH_QUEUE_SERIAL); + info_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, info_queue); + + if (info_timer) + { + // DEBUG_WARN( "created timer\n"); + dispatch_source_set_timer(info_timer, DISPATCH_TIME_NOW, 42ull * NSEC_PER_MSEC, + 100ull * NSEC_PER_MSEC); + dispatch_source_set_event_handler(info_timer, ^{ + // DEBUG_WARN( "dispatch\n"); + mfEvent* event = mf_event_new(FREERDP_SERVER_MAC_EVENT_TYPE_FRAME_TICK); + mf_event_push(info_event_queue, (mfEvent*)event); + }); + dispatch_resume(info_timer); + } + + return TRUE; +} + +static BOOL mf_peer_post_connect(freerdp_peer* client) +{ + mfInfo* mfi = mf_info_get_instance(); + + WINPR_ASSERT(client); + + mfPeerContext* context = (mfPeerContext*)client->context; + WINPR_ASSERT(context); + + rdpSettings* settings = client->context->settings; + WINPR_ASSERT(settings); + + mfi->scale = 1; + // mfi->servscreen_width = 2880 / mfi->scale; + // mfi->servscreen_height = 1800 / mfi->scale; + UINT32 bitsPerPixel = 32; + + if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != mfi->servscreen_width) || + (freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != mfi->servscreen_height)) + { + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, mfi->servscreen_width)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, mfi->servscreen_height)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, bitsPerPixel)) + return FALSE; + + WINPR_ASSERT(client->context->update); + WINPR_ASSERT(client->context->update->DesktopResize); + client->context->update->DesktopResize(client->context); + + mfi->mouse_down_left = FALSE; + mfi->mouse_down_right = FALSE; + mfi->mouse_down_other = FALSE; +#ifdef CHANNEL_RDPSND_SERVER + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpsnd")) + { + mf_peer_rdpsnd_init(context); /* Audio Output */ + } + +#endif + /* Dynamic Virtual Channels */ +#ifdef CHANNEL_AUDIN_SERVER + mf_peer_audin_init(context); /* Audio Input */ +#endif + return TRUE; +} + +static BOOL mf_peer_activate(freerdp_peer* client) +{ + WINPR_ASSERT(client); + + mfPeerContext* context = (mfPeerContext*)client->context; + WINPR_ASSERT(context); + + rdpSettings* settings = client->context->settings; + WINPR_ASSERT(settings); + + rfx_context_reset(context->rfx_context, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + context->activated = TRUE; + return TRUE; +} + +static BOOL mf_peer_synchronize_event(rdpInput* input, UINT32 flags) +{ + return TRUE; +} + +static BOOL mf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code) +{ + bool state_down = FALSE; + + if (flags == KBD_FLAGS_DOWN) + { + state_down = TRUE; + } + return TRUE; +} + +static BOOL mf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) +{ + return FALSE; +} + +static BOOL mf_peer_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area) +{ + return FALSE; +} + +static void* mf_peer_main_loop(void* arg) +{ + mfPeerContext* context; + rdpSettings* settings; + rdpInput* input; + rdpUpdate* update; + freerdp_peer* client = (freerdp_peer*)arg; + + if (!mf_peer_init(client)) + goto fail; + + const mf_server_info* info = client->ContextExtra; + WINPR_ASSERT(info); + + WINPR_ASSERT(client->context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + /* Initialize the real server settings here */ + rdpPrivateKey* key = freerdp_key_new_from_file(info->key); + if (!key) + goto fail; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1)) + goto fail; + rdpCertificate* cert = freerdp_certificate_new_from_file(info->cert); + if (!cert) + goto fail; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1)) + goto fail; + + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE)) + goto fail; + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32)) + goto fail; + + if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, FALSE)) + goto fail; + + client->PostConnect = mf_peer_post_connect; + client->Activate = mf_peer_activate; + + input = client->context->input; + WINPR_ASSERT(input); + + input->SynchronizeEvent = mf_peer_synchronize_event; + input->KeyboardEvent = mf_input_keyboard_event; // mf_peer_keyboard_event; + input->UnicodeKeyboardEvent = mf_peer_unicode_keyboard_event; + input->MouseEvent = mf_input_mouse_event; + input->ExtendedMouseEvent = mf_input_extended_mouse_event; + + update = client->context->update; + WINPR_ASSERT(update); + + // update->RefreshRect = mf_peer_refresh_rect; + update->SuppressOutput = mf_peer_suppress_output; + + WINPR_ASSERT(client->Initialize); + const BOOL rc = client->Initialize(client); + if (!rc) + goto fail; + context = (mfPeerContext*)client->context; + + while (1) + { + DWORD status; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = client->GetEventHandles(client, handles, ARRAYSIZE(handles)); + + if ((count == 0) || (count == MAXIMUM_WAIT_OBJECTS)) + { + WLog_ERR(TAG, "Failed to get FreeRDP file descriptor"); + break; + } + + handles[count++] = WTSVirtualChannelManagerGetEventHandle(context->vcm); + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed"); + break; + } + + if (client->CheckFileDescriptor(client) != TRUE) + { + break; + } + + if ((mf_peer_check_fds(client)) != TRUE) + { + break; + } + + if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE) + { + break; + } + } + + client->Disconnect(client); + freerdp_peer_context_free(client); +fail: + freerdp_peer_free(client); + return NULL; +} + +BOOL mf_peer_accepted(freerdp_listener* instance, freerdp_peer* client) +{ + pthread_t th; + + WINPR_ASSERT(instance); + WINPR_ASSERT(client); + + client->ContextExtra = instance->info; + if (pthread_create(&th, 0, mf_peer_main_loop, client) == 0) + { + pthread_detach(th); + return TRUE; + } + + return FALSE; +} diff --git a/server/Mac/mf_peer.h b/server/Mac/mf_peer.h new file mode 100644 index 0000000..e4a966c --- /dev/null +++ b/server/Mac/mf_peer.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_PEER_H +#define FREERDP_SERVER_MAC_PEER_H + +#include "mf_interface.h" + +typedef struct +{ + const char* cert; + const char* key; +} mf_server_info; + +BOOL mf_peer_accepted(freerdp_listener* instance, freerdp_peer* client); + +#endif /* FREERDP_SERVER_MAC_PEER_H */ diff --git a/server/Mac/mf_rdpsnd.c b/server/Mac/mf_rdpsnd.c new file mode 100644 index 0000000..e1946f2 --- /dev/null +++ b/server/Mac/mf_rdpsnd.c @@ -0,0 +1,200 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * 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 <freerdp/config.h> + +#include <freerdp/server/rdpsnd.h> + +#include "mf_info.h" +#include "mf_rdpsnd.h" +#include "mf_interface.h" + +#include <winpr/sysinfo.h> +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> +#define TAG SERVER_TAG("mac") + +AQRecorderState recorderState; + +static void mf_peer_rdpsnd_activated(RdpsndServerContext* context) +{ + OSStatus status; + BOOL formatAgreed = FALSE; + AUDIO_FORMAT* agreedFormat = NULL; + // we should actually loop through the list of client formats here + // and see if we can send the client something that it supports... + WLog_DBG(TAG, "Client supports the following %d formats: ", context->num_client_formats); + + int i = 0; + for (; i < context->num_client_formats; i++) + { + /* TODO: improve the way we agree on a format */ + for (int j = 0; j < context->num_server_formats; j++) + { + if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) && + (context->client_formats[i].nChannels == context->server_formats[j].nChannels) && + (context->client_formats[i].nSamplesPerSec == + context->server_formats[j].nSamplesPerSec)) + { + WLog_DBG(TAG, "agreed on format!"); + formatAgreed = TRUE; + agreedFormat = (AUDIO_FORMAT*)&context->server_formats[j]; + break; + } + } + + if (formatAgreed == TRUE) + break; + } + + if (formatAgreed == FALSE) + { + WLog_DBG(TAG, "Could not agree on a audio format with the server"); + return; + } + + context->SelectFormat(context, i); + context->SetVolume(context, 0x7FFF, 0x7FFF); + + switch (agreedFormat->wFormatTag) + { + case WAVE_FORMAT_ALAW: + recorderState.dataFormat.mFormatID = kAudioFormatDVIIntelIMA; + break; + + case WAVE_FORMAT_PCM: + recorderState.dataFormat.mFormatID = kAudioFormatLinearPCM; + break; + + default: + recorderState.dataFormat.mFormatID = kAudioFormatLinearPCM; + break; + } + + recorderState.dataFormat.mSampleRate = agreedFormat->nSamplesPerSec; + recorderState.dataFormat.mFormatFlags = + kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + recorderState.dataFormat.mBytesPerPacket = 4; + recorderState.dataFormat.mFramesPerPacket = 1; + recorderState.dataFormat.mBytesPerFrame = 4; + recorderState.dataFormat.mChannelsPerFrame = agreedFormat->nChannels; + recorderState.dataFormat.mBitsPerChannel = agreedFormat->wBitsPerSample; + recorderState.snd_context = context; + status = + AudioQueueNewInput(&recorderState.dataFormat, mf_peer_rdpsnd_input_callback, &recorderState, + NULL, kCFRunLoopCommonModes, 0, &recorderState.queue); + + if (status != noErr) + { + WLog_DBG(TAG, "Failed to create a new Audio Queue. Status code: %" PRId32 "", status); + } + + UInt32 dataFormatSize = sizeof(recorderState.dataFormat); + AudioQueueGetProperty(recorderState.queue, kAudioConverterCurrentInputStreamDescription, + &recorderState.dataFormat, &dataFormatSize); + mf_rdpsnd_derive_buffer_size(recorderState.queue, &recorderState.dataFormat, 0.05, + &recorderState.bufferByteSize); + + for (int i = 0; i < SND_NUMBUFFERS; ++i) + { + AudioQueueAllocateBuffer(recorderState.queue, recorderState.bufferByteSize, + &recorderState.buffers[i]); + AudioQueueEnqueueBuffer(recorderState.queue, recorderState.buffers[i], 0, NULL); + } + + recorderState.currentPacket = 0; + recorderState.isRunning = true; + AudioQueueStart(recorderState.queue, NULL); +} + +BOOL mf_peer_rdpsnd_init(mfPeerContext* context) +{ + context->rdpsnd = rdpsnd_server_context_new(context->vcm); + context->rdpsnd->rdpcontext = &context->_p; + context->rdpsnd->data = context; + context->rdpsnd->num_server_formats = + server_rdpsnd_get_formats(&context->rdpsnd->server_formats); + + if (context->rdpsnd->num_server_formats > 0) + context->rdpsnd->src_format = &context->rdpsnd->server_formats[0]; + + context->rdpsnd->Activated = mf_peer_rdpsnd_activated; + context->rdpsnd->Initialize(context->rdpsnd, TRUE); + return TRUE; +} + +BOOL mf_peer_rdpsnd_stop() +{ + recorderState.isRunning = false; + AudioQueueStop(recorderState.queue, true); + return TRUE; +} + +void mf_peer_rdpsnd_input_callback(void* inUserData, AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer, const AudioTimeStamp* inStartTime, + UInt32 inNumberPacketDescriptions, + const AudioStreamPacketDescription* inPacketDescs) +{ + OSStatus status; + AQRecorderState* rState; + rState = inUserData; + + if (inNumberPacketDescriptions == 0 && rState->dataFormat.mBytesPerPacket != 0) + { + inNumberPacketDescriptions = + inBuffer->mAudioDataByteSize / rState->dataFormat.mBytesPerPacket; + } + + if (rState->isRunning == 0) + { + return; + } + + rState->snd_context->SendSamples(rState->snd_context, inBuffer->mAudioData, + inBuffer->mAudioDataByteSize / 4, + (UINT16)(GetTickCount() & 0xffff)); + status = AudioQueueEnqueueBuffer(rState->queue, inBuffer, 0, NULL); + + if (status != noErr) + { + WLog_DBG(TAG, "AudioQueueEnqueueBuffer() returned status = %" PRId32 "", status); + } +} + +void mf_rdpsnd_derive_buffer_size(AudioQueueRef audioQueue, + AudioStreamBasicDescription* ASBDescription, Float64 seconds, + UInt32* outBufferSize) +{ + static const int maxBufferSize = 0x50000; + int maxPacketSize = ASBDescription->mBytesPerPacket; + + if (maxPacketSize == 0) + { + UInt32 maxVBRPacketSize = sizeof(maxPacketSize); + AudioQueueGetProperty(audioQueue, kAudioQueueProperty_MaximumOutputPacketSize, + // in Mac OS X v10.5, instead use + // kAudioConverterPropertyMaximumOutputPacketSize + &maxPacketSize, &maxVBRPacketSize); + } + + Float64 numBytesForTime = ASBDescription->mSampleRate * maxPacketSize * seconds; + *outBufferSize = (UInt32)(numBytesForTime < maxBufferSize ? numBytesForTime : maxBufferSize); +} diff --git a/server/Mac/mf_rdpsnd.h b/server/Mac/mf_rdpsnd.h new file mode 100644 index 0000000..28bcb0d --- /dev/null +++ b/server/Mac/mf_rdpsnd.h @@ -0,0 +1,58 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_RDPSND_H +#define FREERDP_SERVER_MAC_RDPSND_H + +#include <CoreAudio/CoreAudio.h> +#include <AudioToolbox/AudioToolbox.h> + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/rdpsnd.h> + +#include "mf_types.h" +#include "mfreerdp.h" + +void mf_rdpsnd_derive_buffer_size(AudioQueueRef audioQueue, + AudioStreamBasicDescription* ASBDescription, Float64 seconds, + UInt32* outBufferSize); + +void mf_peer_rdpsnd_input_callback(void* inUserData, AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer, const AudioTimeStamp* inStartTime, + UInt32 inNumberPacketDescriptions, + const AudioStreamPacketDescription* inPacketDescs); + +#define SND_NUMBUFFERS 3 +typedef struct +{ + AudioStreamBasicDescription dataFormat; + AudioQueueRef queue; + AudioQueueBufferRef buffers[SND_NUMBUFFERS]; + AudioFileID audioFile; + UInt32 bufferByteSize; + SInt64 currentPacket; + bool isRunning; + RdpsndServerContext* snd_context; +} AQRecorderState; + +BOOL mf_peer_rdpsnd_init(mfPeerContext* context); +BOOL mf_peer_rdpsnd_stop(void); + +#endif /* FREERDP_SERVER_MAC_RDPSND_H */ diff --git a/server/Mac/mf_types.h b/server/Mac/mf_types.h new file mode 100644 index 0000000..e33be83 --- /dev/null +++ b/server/Mac/mf_types.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Mac OS X Server + * + * Copyright 2023 Armin Novak <anovak@thincst.com> + * Copyright 2023 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. + */ + +#ifndef FREERDP_SERVER_MAC_TYPES_H +#define FREERDP_SERVER_MAC_TYPES_H + +#include <pthread.h> + +#include <freerdp/config.h> + +#include <winpr/crt.h> + +typedef struct mf_info mfInfo; +typedef struct mf_peer_context mfPeerContext; + +#endif /* FREERDP_SERVER_MAC_TYPES_H */ diff --git a/server/Mac/mfreerdp.c b/server/Mac/mfreerdp.c new file mode 100644 index 0000000..8a3fffd --- /dev/null +++ b/server/Mac/mfreerdp.c @@ -0,0 +1,108 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> +#include <signal.h> +#include <sys/time.h> + +#include <CoreGraphics/CGEvent.h> + +#include <winpr/crt.h> +#include <winpr/wtsapi.h> +#include <winpr/assert.h> + +#include <freerdp/freerdp.h> +#include <freerdp/constants.h> +#include <freerdp/channels/wtsvc.h> +#include <freerdp/channels/channels.h> +#include <freerdp/server/server-common.h> + +#include "mfreerdp.h" +#include "mf_peer.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("mac") + +static void mf_server_main_loop(freerdp_listener* instance) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->GetEventHandles); + WINPR_ASSERT(instance->CheckFileDescriptor); + + while (1) + { + DWORD status; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = instance->GetEventHandles(instance, handles, ARRAYSIZE(handles)); + + if (count == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed"); + break; + } + + if (instance->CheckFileDescriptor(instance) != TRUE) + { + break; + } + } + + instance->Close(instance); +} + +int main(int argc, char* argv[]) +{ + freerdp_server_warn_unmaintained(argc, argv); + mf_server_info info = { .key = "server.key", .cert = "server.crt" }; + + freerdp_listener* instance; + + signal(SIGPIPE, SIG_IGN); + + WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()); + + if (!(instance = freerdp_listener_new())) + return 1; + + instance->info = &info; + instance->PeerAccepted = mf_peer_accepted; + + if (instance->Open(instance, NULL, 3389)) + { + mf_server_main_loop(instance); + } + + freerdp_listener_free(instance); + + return 0; +} diff --git a/server/Mac/mfreerdp.h b/server/Mac/mfreerdp.h new file mode 100644 index 0000000..38fa25b --- /dev/null +++ b/server/Mac/mfreerdp.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Mac OS X Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_MAC_FREERDP_H +#define FREERDP_SERVER_MAC_FREERDP_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/codec/rfx.h> + +#endif /* FREERDP_SERVER_MAC_FREERDP_H */ diff --git a/server/Sample/CMakeLists.txt b/server/Sample/CMakeLists.txt new file mode 100644 index 0000000..e23f65c --- /dev/null +++ b/server/Sample/CMakeLists.txt @@ -0,0 +1,86 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Sample Server cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + +set(MODULE_NAME "sfreerdp-server") +set(MODULE_PREFIX "FREERDP_SERVER_SAMPLE") + +set(SRCS + sfreerdp.c + sfreerdp.h + sf_audin.c + sf_audin.h + sf_rdpsnd.c + sf_rdpsnd.h + sf_encomsp.c + sf_encomsp.h) + +if (CHANNEL_AINPUT_SERVER) + list(APPEND SRCS sf_ainput.c sf_ainput.h) +endif() + + # On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + + configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +if (WITH_BINARY_VERSIONING) + set(SAMPLE_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP${FREERDP_VERSION_MAJOR}/images) +else() + set(SAMPLE_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}/FreeRDP/images) +endif() + +set(SAMPLE_ICONS + test_icon.bmp + test_icon.png + test_icon.jpg + test_icon.webp +) +install(FILES ${SAMPLE_ICONS} DESTINATION ${SAMPLE_RESOURCE_ROOT}) + +# We need this in runtime path for TestConnect +file(COPY test_icon.bmp DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +add_executable(${MODULE_NAME} ${SRCS}) +if (WITH_BINARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}") +endif() + +target_compile_definitions(${MODULE_NAME} + PRIVATE + -DSAMPLE_RESOURCE_ROOT="${SAMPLE_RESOURCE_ROOT}" +) +list(APPEND LIBS freerdp-server) +list(APPEND LIBS winpr freerdp) + +target_link_libraries(${MODULE_NAME} ${LIBS}) +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server) +if (WITH_DEBUG_SYMBOLS AND MSVC) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Sample") diff --git a/server/Sample/ModuleOptions.cmake b/server/Sample/ModuleOptions.cmake new file mode 100644 index 0000000..5621f6d --- /dev/null +++ b/server/Sample/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_SERVER_NAME "sfreerdp-server") +set(FREERDP_SERVER_PLATFORM "Sample") +set(FREERDP_SERVER_VENDOR "FreeRDP") diff --git a/server/Sample/sf_ainput.c b/server/Sample/sf_ainput.c new file mode 100644 index 0000000..1c19e07 --- /dev/null +++ b/server/Sample/sf_ainput.c @@ -0,0 +1,92 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Advanced Input) + * + * Copyright 2022 Armin Novak <armin.novak@thincast.com> + * Copyright 2022 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/assert.h> + +#include "sfreerdp.h" + +#include "sf_ainput.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/server/ainput.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("sample.ainput") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sf_peer_ainput_mouse_event(ainput_server_context* context, UINT64 timestamp, + UINT64 flags, INT32 x, INT32 y) +{ + /* TODO: Implement */ + WINPR_ASSERT(context); + + WLog_WARN(TAG, "not implemented: 0x%08" PRIx64 ", 0x%08" PRIx64 ", %" PRId32 "x%" PRId32, + timestamp, flags, x, y); + return CHANNEL_RC_OK; +} + +void sf_peer_ainput_init(testPeerContext* context) +{ + WINPR_ASSERT(context); + + context->ainput = ainput_server_context_new(context->vcm); + WINPR_ASSERT(context->ainput); + + context->ainput->rdpcontext = &context->_p; + context->ainput->data = context; + + context->ainput->MouseEvent = sf_peer_ainput_mouse_event; +} + +BOOL sf_peer_ainput_start(testPeerContext* context) +{ + if (!context || !context->ainput || !context->ainput->Open) + return FALSE; + + return context->ainput->Open(context->ainput) == CHANNEL_RC_OK; +} + +BOOL sf_peer_ainput_stop(testPeerContext* context) +{ + if (!context || !context->ainput || !context->ainput->Close) + return FALSE; + + return context->ainput->Close(context->ainput) == CHANNEL_RC_OK; +} + +BOOL sf_peer_ainput_running(testPeerContext* context) +{ + if (!context || !context->ainput || !context->ainput->IsOpen) + return FALSE; + + return context->ainput->IsOpen(context->ainput); +} + +void sf_peer_ainput_uninit(testPeerContext* context) +{ + ainput_server_context_free(context->ainput); +} diff --git a/server/Sample/sf_ainput.h b/server/Sample/sf_ainput.h new file mode 100644 index 0000000..3cf78d5 --- /dev/null +++ b/server/Sample/sf_ainput.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Advanced Input) + * + * Copyright 2022 Armin Novak <armin.novak@thincast.com> + * Copyright 2022 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. + */ + +#ifndef FREERDP_SERVER_SAMPLE_SF_AINPUT_H +#define FREERDP_SERVER_SAMPLE_SF_AINPUT_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/ainput.h> + +#include "sfreerdp.h" + +void sf_peer_ainput_init(testPeerContext* context); +void sf_peer_ainput_uninit(testPeerContext* context); + +BOOL sf_peer_ainput_running(testPeerContext* context); +BOOL sf_peer_ainput_start(testPeerContext* context); +BOOL sf_peer_ainput_stop(testPeerContext* context); + +#endif /* FREERDP_SERVER_SAMPLE_SF_AINPUT_H */ diff --git a/server/Sample/sf_audin.c b/server/Sample/sf_audin.c new file mode 100644 index 0000000..5783fe6 --- /dev/null +++ b/server/Sample/sf_audin.c @@ -0,0 +1,112 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Audio Input) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de> + * + * 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/assert.h> + +#include "sfreerdp.h" + +#include "sf_audin.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> +#define TAG SERVER_TAG("sample") + +#if defined(CHANNEL_AUDIN_SERVER) + +static UINT sf_peer_audin_data(audin_server_context* audin, const SNDIN_DATA* data) +{ + /* TODO: Implement */ + WINPR_ASSERT(audin); + WINPR_ASSERT(data); + + WLog_WARN(TAG, "not implemented"); + WLog_DBG(TAG, "receive %" PRIdz " bytes.", Stream_Length(data->Data)); + return CHANNEL_RC_OK; +} + +#endif + +BOOL sf_peer_audin_init(testPeerContext* context) +{ + WINPR_ASSERT(context); +#if defined(CHANNEL_AUDIN_SERVER) + context->audin = audin_server_context_new(context->vcm); + WINPR_ASSERT(context->audin); + + context->audin->rdpcontext = &context->_p; + context->audin->userdata = context; + + context->audin->Data = sf_peer_audin_data; + + return audin_server_set_formats(context->audin, -1, NULL); +#else + return TRUE; +#endif +} + +BOOL sf_peer_audin_start(testPeerContext* context) +{ +#if defined(CHANNEL_AUDIN_SERVER) + if (!context || !context->audin || !context->audin->Open) + return FALSE; + + return context->audin->Open(context->audin); +#else + return FALSE; +#endif +} + +BOOL sf_peer_audin_stop(testPeerContext* context) +{ +#if defined(CHANNEL_AUDIN_SERVER) + if (!context || !context->audin || !context->audin->Close) + return FALSE; + + return context->audin->Close(context->audin); +#else + return FALSE; +#endif +} + +BOOL sf_peer_audin_running(testPeerContext* context) +{ +#if defined(CHANNEL_AUDIN_SERVER) + if (!context || !context->audin || !context->audin->IsOpen) + return FALSE; + + return context->audin->IsOpen(context->audin); +#else + return FALSE; +#endif +} + +void sf_peer_audin_uninit(testPeerContext* context) +{ + WINPR_ASSERT(context); + +#if defined(CHANNEL_AUDIN_SERVER) + audin_server_context_free(context->audin); + context->audin = NULL; +#endif +} diff --git a/server/Sample/sf_audin.h b/server/Sample/sf_audin.h new file mode 100644 index 0000000..1769603 --- /dev/null +++ b/server/Sample/sf_audin.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Audio Input) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SAMPLE_SF_AUDIN_H +#define FREERDP_SERVER_SAMPLE_SF_AUDIN_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> + +#include "sfreerdp.h" + +BOOL sf_peer_audin_init(testPeerContext* context); +void sf_peer_audin_uninit(testPeerContext* context); + +BOOL sf_peer_audin_running(testPeerContext* context); +BOOL sf_peer_audin_start(testPeerContext* context); +BOOL sf_peer_audin_stop(testPeerContext* context); + +#endif /* FREERDP_SERVER_SAMPLE_SF_AUDIN_H */ diff --git a/server/Sample/sf_encomsp.c b/server/Sample/sf_encomsp.c new file mode 100644 index 0000000..6d03f79 --- /dev/null +++ b/server/Sample/sf_encomsp.c @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Lync Multiparty) + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * 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 <freerdp/config.h> + +#include <winpr/assert.h> + +#include "sf_encomsp.h" + +BOOL sf_peer_encomsp_init(testPeerContext* context) +{ + WINPR_ASSERT(context); + + context->encomsp = encomsp_server_context_new(context->vcm); + if (!context->encomsp) + return FALSE; + + context->encomsp->rdpcontext = &context->_p; + + WINPR_ASSERT(context->encomsp->Start); + if (context->encomsp->Start(context->encomsp) != CHANNEL_RC_OK) + return FALSE; + + return TRUE; +} diff --git a/server/Sample/sf_encomsp.h b/server/Sample/sf_encomsp.h new file mode 100644 index 0000000..7976abc --- /dev/null +++ b/server/Sample/sf_encomsp.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Lync Multiparty) + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H +#define FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/encomsp.h> + +#include "sfreerdp.h" + +BOOL sf_peer_encomsp_init(testPeerContext* context); + +#endif /* FREERDP_SERVER_SAMPLE_SF_ENCOMSP_H */ diff --git a/server/Sample/sf_rdpsnd.c b/server/Sample/sf_rdpsnd.c new file mode 100644 index 0000000..6d4c1ec --- /dev/null +++ b/server/Sample/sf_rdpsnd.c @@ -0,0 +1,62 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * 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 <freerdp/config.h> + +#include <winpr/assert.h> + +#include "sf_rdpsnd.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> +#define TAG SERVER_TAG("sample") + +static void sf_peer_rdpsnd_activated(RdpsndServerContext* context) +{ + WINPR_UNUSED(context); + WINPR_ASSERT(context); + WLog_DBG(TAG, "RDPSND Activated"); +} + +BOOL sf_peer_rdpsnd_init(testPeerContext* context) +{ + WINPR_ASSERT(context); + + context->rdpsnd = rdpsnd_server_context_new(context->vcm); + WINPR_ASSERT(context->rdpsnd); + context->rdpsnd->rdpcontext = &context->_p; + context->rdpsnd->data = context; + context->rdpsnd->num_server_formats = + server_rdpsnd_get_formats(&context->rdpsnd->server_formats); + + if (context->rdpsnd->num_server_formats > 0) + context->rdpsnd->src_format = &context->rdpsnd->server_formats[0]; + + context->rdpsnd->Activated = sf_peer_rdpsnd_activated; + + WINPR_ASSERT(context->rdpsnd->Initialize); + if (context->rdpsnd->Initialize(context->rdpsnd, TRUE) != CHANNEL_RC_OK) + { + return FALSE; + } + + return TRUE; +} diff --git a/server/Sample/sf_rdpsnd.h b/server/Sample/sf_rdpsnd.h new file mode 100644 index 0000000..f9b0ef4 --- /dev/null +++ b/server/Sample/sf_rdpsnd.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SAMPLE_SF_RDPSND_H +#define FREERDP_SERVER_SAMPLE_SF_RDPSND_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/rdpsnd.h> + +#include "sfreerdp.h" + +BOOL sf_peer_rdpsnd_init(testPeerContext* context); + +#endif /* FREERDP_SERVER_SAMPLE_SF_RDPSND_H */ diff --git a/server/Sample/sfreerdp.c b/server/Sample/sfreerdp.c new file mode 100644 index 0000000..f39a747 --- /dev/null +++ b/server/Sample/sfreerdp.c @@ -0,0 +1,1440 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Test Server + * + * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2011 Vic Lee + * Copyright 2014 Norbert Federa <norbert.federa@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 <freerdp/config.h> + +#include <errno.h> +#include <signal.h> + +#include <winpr/winpr.h> +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/ssl.h> +#include <winpr/synch.h> +#include <winpr/file.h> +#include <winpr/string.h> +#include <winpr/path.h> +#include <winpr/image.h> +#include <winpr/winsock.h> + +#include <freerdp/streamdump.h> +#include <freerdp/transport_io.h> + +#include <freerdp/channels/wtsvc.h> +#include <freerdp/channels/channels.h> +#include <freerdp/channels/drdynvc.h> + +#include <freerdp/freerdp.h> +#include <freerdp/constants.h> +#include <freerdp/server/rdpsnd.h> +#include <freerdp/settings.h> + +#include "sf_ainput.h" +#include "sf_audin.h" +#include "sf_rdpsnd.h" +#include "sf_encomsp.h" + +#include "sfreerdp.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("sample") + +#define SAMPLE_SERVER_USE_CLIENT_RESOLUTION 1 +#define SAMPLE_SERVER_DEFAULT_WIDTH 1024 +#define SAMPLE_SERVER_DEFAULT_HEIGHT 768 + +struct server_info +{ + BOOL test_dump_rfx_realtime; + const char* test_pcap_file; + const char* replay_dump; + const char* cert; + const char* key; +}; + +static void test_peer_context_free(freerdp_peer* client, rdpContext* ctx) +{ + testPeerContext* context = (testPeerContext*)ctx; + + WINPR_UNUSED(client); + + if (context) + { + winpr_image_free(context->image, TRUE); + if (context->debug_channel_thread) + { + WINPR_ASSERT(context->stopEvent); + SetEvent(context->stopEvent); + WaitForSingleObject(context->debug_channel_thread, INFINITE); + CloseHandle(context->debug_channel_thread); + } + + Stream_Free(context->s, TRUE); + free(context->bg_data); + rfx_context_free(context->rfx_context); + nsc_context_free(context->nsc_context); + + if (context->debug_channel) + WTSVirtualChannelClose(context->debug_channel); + + sf_peer_audin_uninit(context); + +#if defined(CHANNEL_AINPUT_SERVER) + sf_peer_ainput_uninit(context); +#endif + + rdpsnd_server_context_free(context->rdpsnd); + encomsp_server_context_free(context->encomsp); + + WTSCloseServer((HANDLE)context->vcm); + } +} + +static BOOL test_peer_context_new(freerdp_peer* client, rdpContext* ctx) +{ + testPeerContext* context = (testPeerContext*)ctx; + + WINPR_ASSERT(client); + WINPR_ASSERT(context); + WINPR_ASSERT(ctx->settings); + + context->image = winpr_image_new(); + if (!context->image) + goto fail; + if (!(context->rfx_context = rfx_context_new_ex( + TRUE, freerdp_settings_get_uint32(ctx->settings, FreeRDP_ThreadingFlags)))) + goto fail; + + if (!rfx_context_reset(context->rfx_context, SAMPLE_SERVER_DEFAULT_WIDTH, + SAMPLE_SERVER_DEFAULT_HEIGHT)) + goto fail; + + rfx_context_set_mode(context->rfx_context, RLGR3); + + if (!(context->nsc_context = nsc_context_new())) + goto fail; + + if (!(context->s = Stream_New(NULL, 65536))) + goto fail; + + context->icon_x = UINT32_MAX; + context->icon_y = UINT32_MAX; + context->vcm = WTSOpenServerA((LPSTR)client->context); + + if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE) + goto fail; + + return TRUE; +fail: + test_peer_context_free(client, ctx); + return FALSE; +} + +static BOOL test_peer_init(freerdp_peer* client) +{ + WINPR_ASSERT(client); + + client->ContextSize = sizeof(testPeerContext); + client->ContextNew = test_peer_context_new; + client->ContextFree = test_peer_context_free; + return freerdp_peer_context_new(client); +} + +static wStream* test_peer_stream_init(testPeerContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->s); + + Stream_Clear(context->s); + Stream_SetPosition(context->s, 0); + return context->s; +} + +static void test_peer_begin_frame(freerdp_peer* client) +{ + rdpUpdate* update = NULL; + SURFACE_FRAME_MARKER fm = { 0 }; + testPeerContext* context = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->context); + + update = client->context->update; + WINPR_ASSERT(update); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + fm.frameAction = SURFACECMD_FRAMEACTION_BEGIN; + fm.frameId = context->frame_id; + WINPR_ASSERT(update->SurfaceFrameMarker); + update->SurfaceFrameMarker(update->context, &fm); +} + +static void test_peer_end_frame(freerdp_peer* client) +{ + rdpUpdate* update = NULL; + SURFACE_FRAME_MARKER fm = { 0 }; + testPeerContext* context = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + update = client->context->update; + WINPR_ASSERT(update); + + fm.frameAction = SURFACECMD_FRAMEACTION_END; + fm.frameId = context->frame_id; + WINPR_ASSERT(update->SurfaceFrameMarker); + update->SurfaceFrameMarker(update->context, &fm); + context->frame_id++; +} + +static BOOL test_peer_draw_background(freerdp_peer* client) +{ + size_t size = 0; + wStream* s = NULL; + RFX_RECT rect; + BYTE* rgb_data = NULL; + const rdpSettings* settings = NULL; + rdpUpdate* update = NULL; + SURFACE_BITS_COMMAND cmd = { 0 }; + testPeerContext* context = NULL; + BOOL ret = FALSE; + const UINT32 colorFormat = PIXEL_FORMAT_RGB24; + const size_t bpp = FreeRDPGetBytesPerPixel(colorFormat); + + WINPR_ASSERT(client); + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + update = client->context->update; + WINPR_ASSERT(update); + + const BOOL RemoteFxCodec = freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec); + if (!RemoteFxCodec && !freerdp_settings_get_bool(settings, FreeRDP_NSCodec)) + return FALSE; + + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX); + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX); + + s = test_peer_stream_init(context); + rect.x = 0; + rect.y = 0; + rect.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + rect.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + size = bpp * rect.width * rect.height; + + if (!(rgb_data = malloc(size))) + { + WLog_ERR(TAG, "Problem allocating memory"); + return FALSE; + } + + memset(rgb_data, 0xA0, size); + + if (RemoteFxCodec) + { + WLog_DBG(TAG, "Using RemoteFX codec"); + rfx_context_set_pixel_format(context->rfx_context, colorFormat); + if (!rfx_compose_message(context->rfx_context, s, &rect, 1, rgb_data, rect.width, + rect.height, rect.width * bpp)) + { + goto out; + } + + const UINT32 RemoteFxCodecId = + freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId); + WINPR_ASSERT(RemoteFxCodecId <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)RemoteFxCodecId; + cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; + } + else + { + WLog_DBG(TAG, "Using NSCodec"); + nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat); + nsc_compose_message(context->nsc_context, s, rgb_data, rect.width, rect.height, + rect.width * bpp); + const UINT32 NSCodecId = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId); + WINPR_ASSERT(NSCodecId <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)NSCodecId; + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; + } + + cmd.destLeft = 0; + cmd.destTop = 0; + cmd.destRight = rect.width; + cmd.destBottom = rect.height; + cmd.bmp.bpp = 32; + cmd.bmp.flags = 0; + cmd.bmp.width = rect.width; + cmd.bmp.height = rect.height; + WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX); + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + test_peer_begin_frame(client); + update->SurfaceBits(update->context, &cmd); + test_peer_end_frame(client); + ret = TRUE; +out: + free(rgb_data); + return ret; +} + +static int open_icon(wImage* img) +{ + char* paths[] = { SAMPLE_RESOURCE_ROOT, "." }; + const char* names[] = { "test_icon.webp", "test_icon.png", "test_icon.jpg", "test_icon.bmp" }; + + for (size_t x = 0; x < ARRAYSIZE(paths); x++) + { + const char* path = paths[x]; + if (!winpr_PathFileExists(path)) + continue; + + for (size_t y = 0; y < ARRAYSIZE(names); y++) + { + const char* name = names[y]; + char* file = GetCombinedPath(path, name); + int rc = winpr_image_read(img, file); + free(file); + if (rc > 0) + return rc; + } + } + WLog_ERR(TAG, "Unable to open test icon"); + return -1; +} + +static BOOL test_peer_load_icon(freerdp_peer* client) +{ + testPeerContext* context = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && + !freerdp_settings_get_bool(settings, FreeRDP_NSCodec)) + { + WLog_ERR(TAG, "Client doesn't support RemoteFX or NSCodec"); + return FALSE; + } + + int rc = open_icon(context->image); + if (rc <= 0) + goto out_fail; + + /* background with same size, which will be used to erase the icon from old position */ + if (!(context->bg_data = calloc(context->image->height, context->image->width * 3))) + goto out_fail; + + memset(context->bg_data, 0xA0, context->image->height * context->image->width * 3ull); + return TRUE; +out_fail: + context->bg_data = NULL; + return FALSE; +} + +static void test_peer_draw_icon(freerdp_peer* client, UINT32 x, UINT32 y) +{ + wStream* s = NULL; + RFX_RECT rect; + rdpUpdate* update = NULL; + rdpSettings* settings = NULL; + SURFACE_BITS_COMMAND cmd = { 0 }; + testPeerContext* context = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + update = client->context->update; + WINPR_ASSERT(update); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + if (freerdp_settings_get_bool(settings, FreeRDP_DumpRemoteFx)) + return; + + if (context->image->width < 1 || !context->activated) + return; + + rect.x = 0; + rect.y = 0; + rect.width = context->image->width; + rect.height = context->image->height; + + const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + if (context->icon_x + context->image->width > w) + return; + if (context->icon_y + context->image->height > h) + return; + if (x + context->image->width > w) + return; + if (y + context->image->height > h) + return; + + test_peer_begin_frame(client); + const BOOL RemoteFxCodec = freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec); + if (RemoteFxCodec) + { + const UINT32 RemoteFxCodecId = + freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId); + WINPR_ASSERT(RemoteFxCodecId <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)RemoteFxCodecId; + cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; + } + else + { + const UINT32 NSCodecId = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId); + WINPR_ASSERT(NSCodecId <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)NSCodecId; + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; + } + + if (context->icon_x != UINT32_MAX) + { + const UINT32 colorFormat = PIXEL_FORMAT_RGB24; + const UINT32 bpp = FreeRDPGetBytesPerPixel(colorFormat); + s = test_peer_stream_init(context); + + if (RemoteFxCodec) + { + rfx_context_set_pixel_format(context->rfx_context, colorFormat); + rfx_compose_message(context->rfx_context, s, &rect, 1, context->bg_data, rect.width, + rect.height, rect.width * bpp); + } + else + { + nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat); + nsc_compose_message(context->nsc_context, s, context->bg_data, rect.width, rect.height, + rect.width * bpp); + } + + cmd.destLeft = context->icon_x; + cmd.destTop = context->icon_y; + cmd.destRight = context->icon_x + rect.width; + cmd.destBottom = context->icon_y + rect.height; + cmd.bmp.bpp = 32; + cmd.bmp.flags = 0; + cmd.bmp.width = rect.width; + cmd.bmp.height = rect.height; + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + WINPR_ASSERT(update->SurfaceBits); + update->SurfaceBits(update->context, &cmd); + } + + s = test_peer_stream_init(context); + + { + const UINT32 colorFormat = + context->image->bitsPerPixel > 24 ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_BGR24; + + if (RemoteFxCodec) + { + rfx_context_set_pixel_format(context->rfx_context, colorFormat); + rfx_compose_message(context->rfx_context, s, &rect, 1, context->image->data, rect.width, + rect.height, context->image->scanline); + } + else + { + nsc_context_set_parameters(context->nsc_context, NSC_COLOR_FORMAT, colorFormat); + nsc_compose_message(context->nsc_context, s, context->image->data, rect.width, + rect.height, context->image->scanline); + } + } + + cmd.destLeft = x; + cmd.destTop = y; + cmd.destRight = x + rect.width; + cmd.destBottom = y + rect.height; + cmd.bmp.bpp = 32; + cmd.bmp.width = rect.width; + cmd.bmp.height = rect.height; + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + WINPR_ASSERT(update->SurfaceBits); + update->SurfaceBits(update->context, &cmd); + context->icon_x = x; + context->icon_y = y; + test_peer_end_frame(client); +} + +static BOOL test_sleep_tsdiff(UINT32* old_sec, UINT32* old_usec, UINT32 new_sec, UINT32 new_usec) +{ + INT64 sec = 0; + INT64 usec = 0; + + WINPR_ASSERT(old_sec); + WINPR_ASSERT(old_usec); + + if ((*old_sec == 0) && (*old_usec == 0)) + { + *old_sec = new_sec; + *old_usec = new_usec; + return TRUE; + } + + sec = new_sec - *old_sec; + usec = new_usec - *old_usec; + + if ((sec < 0) || ((sec == 0) && (usec < 0))) + { + WLog_ERR(TAG, "Invalid time stamp detected."); + return FALSE; + } + + *old_sec = new_sec; + *old_usec = new_usec; + + while (usec < 0) + { + usec += 1000000; + sec--; + } + + if (sec > 0) + Sleep((DWORD)sec * 1000); + + if (usec > 0) + USleep((DWORD)usec); + + return TRUE; +} + +static BOOL tf_peer_dump_rfx(freerdp_peer* client) +{ + wStream* s = NULL; + UINT32 prev_seconds = 0; + UINT32 prev_useconds = 0; + rdpUpdate* update = NULL; + rdpPcap* pcap_rfx = NULL; + pcap_record record = { 0 }; + struct server_info* info = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->context); + + info = client->ContextExtra; + WINPR_ASSERT(info); + + s = Stream_New(NULL, 512); + + if (!s) + return FALSE; + + update = client->context->update; + WINPR_ASSERT(update); + + if (!(pcap_rfx = pcap_open(info->test_pcap_file, FALSE))) + return FALSE; + + prev_seconds = prev_useconds = 0; + + while (pcap_has_next_record(pcap_rfx)) + { + if (!pcap_get_next_record_header(pcap_rfx, &record)) + break; + + if (!Stream_EnsureCapacity(s, record.length)) + break; + + record.data = Stream_Buffer(s); + pcap_get_next_record_content(pcap_rfx, &record); + Stream_SetPosition(s, Stream_Capacity(s)); + + if (info->test_dump_rfx_realtime && + test_sleep_tsdiff(&prev_seconds, &prev_useconds, record.header.ts_sec, + record.header.ts_usec) == FALSE) + break; + + WINPR_ASSERT(update->SurfaceCommand); + update->SurfaceCommand(update->context, s); + + WINPR_ASSERT(client->CheckFileDescriptor); + if (client->CheckFileDescriptor(client) != TRUE) + break; + } + + Stream_Free(s, TRUE); + pcap_close(pcap_rfx); + return TRUE; +} + +static DWORD WINAPI tf_debug_channel_thread_func(LPVOID arg) +{ + void* fd = NULL; + wStream* s = NULL; + void* buffer = NULL; + DWORD BytesReturned = 0; + ULONG written = 0; + testPeerContext* context = (testPeerContext*)arg; + + WINPR_ASSERT(context); + if (WTSVirtualChannelQuery(context->debug_channel, WTSVirtualFileHandle, &buffer, + &BytesReturned) == TRUE) + { + fd = *((void**)buffer); + WTSFreeMemory(buffer); + + if (!(context->event = CreateWaitObjectEvent(NULL, TRUE, FALSE, fd))) + return 0; + } + + s = Stream_New(NULL, 4096); + WTSVirtualChannelWrite(context->debug_channel, (PCHAR) "test1", 5, &written); + + while (1) + { + DWORD status = 0; + DWORD nCount = 0; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + + handles[nCount++] = context->event; + handles[nCount++] = freerdp_abort_event(&context->_p); + handles[nCount++] = context->stopEvent; + status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + default: + goto fail; + } + + Stream_SetPosition(s, 0); + + if (WTSVirtualChannelRead(context->debug_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + if (BytesReturned == 0) + break; + + Stream_EnsureRemainingCapacity(s, BytesReturned); + + if (WTSVirtualChannelRead(context->debug_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + /* should not happen */ + break; + } + } + + Stream_SetPosition(s, BytesReturned); + WLog_DBG(TAG, "got %" PRIu32 " bytes", BytesReturned); + } +fail: + Stream_Free(s, TRUE); + return 0; +} + +static BOOL tf_peer_post_connect(freerdp_peer* client) +{ + testPeerContext* context = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + /** + * This callback is called when the entire connection sequence is done, i.e. we've received the + * Font List PDU from the client and sent out the Font Map PDU. + * The server may start sending graphics output and receiving keyboard/mouse input after this + * callback returns. + */ + WLog_DBG(TAG, "Client %s is activated (osMajorType %" PRIu32 " osMinorType %" PRIu32 ")", + client->local ? "(local)" : client->hostname, + freerdp_settings_get_uint32(settings, FreeRDP_OsMajorType), + freerdp_settings_get_uint32(settings, FreeRDP_OsMinorType)); + + if (freerdp_settings_get_bool(settings, FreeRDP_AutoLogonEnabled)) + { + const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username); + const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain); + WLog_DBG(TAG, " and wants to login automatically as %s\\%s", Domain ? Domain : "", + Username); + /* A real server may perform OS login here if NLA is not executed previously. */ + } + + WLog_DBG(TAG, ""); + WLog_DBG(TAG, "Client requested desktop: %" PRIu32 "x%" PRIu32 "x%" PRIu32 "", + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)); +#if (SAMPLE_SERVER_USE_CLIENT_RESOLUTION == 1) + + if (!rfx_context_reset(context->rfx_context, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + return FALSE; + + WLog_DBG(TAG, "Using resolution requested by client."); +#else + client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) = + context->rfx_context->width; + client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) = + context->rfx_context->height; + WLog_DBG(TAG, "Resizing client to %" PRIu32 "x%" PRIu32 "", + client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + client->freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + client->update->DesktopResize(client->update->context); +#endif + + /* A real server should tag the peer as activated here and start sending updates in main loop. + */ + if (!test_peer_load_icon(client)) + { + WLog_DBG(TAG, "Unable to load icon"); + return FALSE; + } + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpdbg")) + { + context->debug_channel = WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, "rdpdbg"); + + if (context->debug_channel != NULL) + { + WLog_DBG(TAG, "Open channel rdpdbg."); + + if (!(context->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_ERR(TAG, "Failed to create stop event"); + return FALSE; + } + + if (!(context->debug_channel_thread = + CreateThread(NULL, 0, tf_debug_channel_thread_func, (void*)context, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create debug channel thread"); + CloseHandle(context->stopEvent); + context->stopEvent = NULL; + return FALSE; + } + } + } + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, RDPSND_CHANNEL_NAME)) + { + sf_peer_rdpsnd_init(context); /* Audio Output */ + } + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, ENCOMSP_SVC_CHANNEL_NAME)) + { + sf_peer_encomsp_init(context); /* Lync Multiparty */ + } + + /* Dynamic Virtual Channels */ + sf_peer_audin_init(context); /* Audio Input */ + +#if defined(CHANNEL_AINPUT_SERVER) + sf_peer_ainput_init(context); +#endif + + /* Return FALSE here would stop the execution of the peer main loop. */ + return TRUE; +} + +static BOOL tf_peer_activate(freerdp_peer* client) +{ + testPeerContext* context = NULL; + struct server_info* info = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + info = client->ContextExtra; + WINPR_ASSERT(info); + + context->activated = TRUE; + // PACKET_COMPR_TYPE_8K; + // PACKET_COMPR_TYPE_64K; + // PACKET_COMPR_TYPE_RDP6; + if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8)) + return FALSE; + + if (info->test_pcap_file != NULL) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_DumpRemoteFx, TRUE)) + return FALSE; + + if (!tf_peer_dump_rfx(client)) + return FALSE; + } + else + test_peer_draw_background(client); + + return TRUE; +} + +static BOOL tf_peer_synchronize_event(rdpInput* input, UINT32 flags) +{ + WINPR_UNUSED(input); + WINPR_ASSERT(input); + WLog_DBG(TAG, "Client sent a synchronize event (flags:0x%" PRIX32 ")", flags); + return TRUE; +} + +static BOOL tf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code) +{ + freerdp_peer* client = NULL; + rdpUpdate* update = NULL; + rdpContext* context = NULL; + testPeerContext* tcontext = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(input); + + context = input->context; + WINPR_ASSERT(context); + + client = context->peer; + WINPR_ASSERT(client); + + settings = context->settings; + WINPR_ASSERT(settings); + + update = context->update; + WINPR_ASSERT(update); + + tcontext = (testPeerContext*)context; + WINPR_ASSERT(tcontext); + + WLog_DBG(TAG, "Client sent a keyboard event (flags:0x%04" PRIX16 " code:0x%04" PRIX8 ")", flags, + code); + + if (((flags & KBD_FLAGS_RELEASE) == 0) && (code == RDP_SCANCODE_KEY_G)) /* 'g' key */ + { + if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != 800) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 800)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 600)) + return FALSE; + } + else + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, + SAMPLE_SERVER_DEFAULT_WIDTH)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, + SAMPLE_SERVER_DEFAULT_HEIGHT)) + return FALSE; + } + + if (!rfx_context_reset(tcontext->rfx_context, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + return FALSE; + + WINPR_ASSERT(update->DesktopResize); + update->DesktopResize(update->context); + tcontext->activated = FALSE; + } + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_C) /* 'c' key */ + { + if (tcontext->debug_channel) + { + ULONG written = 0; + WTSVirtualChannelWrite(tcontext->debug_channel, (PCHAR) "test2", 5, &written); + } + } + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_X) /* 'x' key */ + { + WINPR_ASSERT(client->Close); + client->Close(client); + } + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_R) /* 'r' key */ + { + tcontext->audin_open = !tcontext->audin_open; + } +#if defined(CHANNEL_AINPUT_SERVER) + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_I) /* 'i' key */ + { + tcontext->ainput_open = !tcontext->ainput_open; + } +#endif + else if (((flags & KBD_FLAGS_RELEASE) == 0) && code == RDP_SCANCODE_KEY_S) /* 's' key */ + { + } + + return TRUE; +} + +static BOOL tf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) +{ + WINPR_UNUSED(input); + WINPR_ASSERT(input); + + WLog_DBG(TAG, + "Client sent a unicode keyboard event (flags:0x%04" PRIX16 " code:0x%04" PRIX16 ")", + flags, code); + return TRUE; +} + +static BOOL tf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + WINPR_UNUSED(flags); + WINPR_ASSERT(input); + WINPR_ASSERT(input->context); + + // WLog_DBG(TAG, "Client sent a mouse event (flags:0x%04"PRIX16" pos:%"PRIu16",%"PRIu16")", + // flags, x, y); + test_peer_draw_icon(input->context->peer, x + 10, y); + return TRUE; +} + +static BOOL tf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + WINPR_UNUSED(flags); + WINPR_ASSERT(input); + WINPR_ASSERT(input->context); + + // WLog_DBG(TAG, "Client sent an extended mouse event (flags:0x%04"PRIX16" + // pos:%"PRIu16",%"PRIu16")", flags, x, y); + test_peer_draw_icon(input->context->peer, x + 10, y); + return TRUE; +} + +static BOOL tf_peer_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas) +{ + WINPR_UNUSED(context); + WINPR_ASSERT(context); + WINPR_ASSERT(areas || (count == 0)); + + WLog_DBG(TAG, "Client requested to refresh:"); + + for (BYTE i = 0; i < count; i++) + { + WLog_DBG(TAG, " (%" PRIu16 ", %" PRIu16 ") (%" PRIu16 ", %" PRIu16 ")", areas[i].left, + areas[i].top, areas[i].right, areas[i].bottom); + } + + return TRUE; +} + +static BOOL tf_peer_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area) +{ + WINPR_UNUSED(context); + + if (allow > 0) + { + WINPR_ASSERT(area); + WLog_DBG(TAG, + "Client restore output (%" PRIu16 ", %" PRIu16 ") (%" PRIu16 ", %" PRIu16 ").", + area->left, area->top, area->right, area->bottom); + } + else + { + WLog_DBG(TAG, "Client minimized and suppress output."); + } + + return TRUE; +} + +static int hook_peer_write_pdu(rdpTransport* transport, wStream* s) +{ + UINT64 ts = 0; + wStream* ls = NULL; + UINT64 last_ts = 0; + const struct server_info* info = NULL; + freerdp_peer* client = NULL; + testPeerContext* peerCtx = NULL; + size_t offset = 0; + UINT32 flags = 0; + rdpContext* context = transport_get_context(transport); + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + client = context->peer; + WINPR_ASSERT(client); + + peerCtx = (testPeerContext*)client->context; + WINPR_ASSERT(peerCtx); + WINPR_ASSERT(peerCtx->io.WritePdu); + + info = client->ContextExtra; + WINPR_ASSERT(info); + + /* Let the client authenticate. + * After that is done, we stop the normal operation and send + * a previously recorded session PDU by PDU to the client. + * + * This is fragile and the connecting client needs to use the same + * configuration as the one that recorded the session! + */ + WINPR_ASSERT(info); + CONNECTION_STATE state = freerdp_get_state(context); + if (state < CONNECTION_STATE_NEGO) + return peerCtx->io.WritePdu(transport, s); + + ls = Stream_New(NULL, 4096); + if (!ls) + goto fail; + + while (stream_dump_get(context, &flags, ls, &offset, &ts) > 0) + { + int rc = 0; + /* Skip messages from client. */ + if (flags & STREAM_MSG_SRV_TX) + { + if ((last_ts > 0) && (ts > last_ts)) + { + UINT64 diff = ts - last_ts; + Sleep(diff); + } + last_ts = ts; + rc = peerCtx->io.WritePdu(transport, ls); + if (rc < 0) + goto fail; + } + Stream_SetPosition(ls, 0); + } + +fail: + Stream_Free(ls, TRUE); + return -1; +} + +static DWORD WINAPI test_peer_mainloop(LPVOID arg) +{ + BOOL rc = 0; + DWORD error = CHANNEL_RC_OK; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = 0; + DWORD status = 0; + testPeerContext* context = NULL; + struct server_info* info = NULL; + rdpSettings* settings = NULL; + rdpInput* input = NULL; + rdpUpdate* update = NULL; + freerdp_peer* client = (freerdp_peer*)arg; + + WINPR_ASSERT(client); + + info = client->ContextExtra; + WINPR_ASSERT(info); + + if (!test_peer_init(client)) + { + freerdp_peer_free(client); + return 0; + } + + /* Initialize the real server settings here */ + WINPR_ASSERT(client->context); + settings = client->context->settings; + WINPR_ASSERT(settings); + if (info->replay_dump) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE) || + !freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, info->replay_dump)) + goto fail; + } + + rdpPrivateKey* key = freerdp_key_new_from_file(info->key); + if (!key) + goto fail; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1)) + goto fail; + rdpCertificate* cert = freerdp_certificate_new_from_file(info->cert); + if (!cert) + goto fail; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1)) + goto fail; + + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + goto fail; + if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel, + ENCRYPTION_LEVEL_CLIENT_COMPATIBLE)) + goto fail; + /* ENCRYPTION_LEVEL_HIGH; */ + /* ENCRYPTION_LEVEL_LOW; */ + /* ENCRYPTION_LEVEL_FIPS; */ + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE) || + !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32)) + goto fail; + + if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE)) + goto fail; + + client->PostConnect = tf_peer_post_connect; + client->Activate = tf_peer_activate; + + WINPR_ASSERT(client->context); + input = client->context->input; + WINPR_ASSERT(input); + + input->SynchronizeEvent = tf_peer_synchronize_event; + input->KeyboardEvent = tf_peer_keyboard_event; + input->UnicodeKeyboardEvent = tf_peer_unicode_keyboard_event; + input->MouseEvent = tf_peer_mouse_event; + input->ExtendedMouseEvent = tf_peer_extended_mouse_event; + + update = client->context->update; + WINPR_ASSERT(update); + + update->RefreshRect = tf_peer_refresh_rect; + update->SuppressOutput = tf_peer_suppress_output; + if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize, + 0xFFFFFF /* FIXME */)) + goto fail; + + WINPR_ASSERT(client->Initialize); + rc = client->Initialize(client); + if (!rc) + goto fail; + + context = (testPeerContext*)client->context; + WINPR_ASSERT(context); + + if (info->replay_dump) + { + const rdpTransportIo* cb = freerdp_get_io_callbacks(client->context); + rdpTransportIo replay; + + WINPR_ASSERT(cb); + replay = *cb; + context->io = *cb; + replay.WritePdu = hook_peer_write_pdu; + freerdp_set_io_callbacks(client->context, &replay); + } + + WLog_INFO(TAG, "We've got a client %s", client->local ? "(local)" : client->hostname); + + while (error == CHANNEL_RC_OK) + { + count = 0; + { + WINPR_ASSERT(client->GetEventHandles); + DWORD tmp = client->GetEventHandles(client, &handles[count], 32 - count); + + if (tmp == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP transport event handles"); + break; + } + + count += tmp; + } + + HANDLE channelHandle = WTSVirtualChannelManagerGetEventHandle(context->vcm); + handles[count++] = channelHandle; + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed (errno: %d)", errno); + break; + } + + WINPR_ASSERT(client->CheckFileDescriptor); + if (client->CheckFileDescriptor(client) != TRUE) + break; + + if (WaitForSingleObject(channelHandle, 0) != WAIT_OBJECT_0) + continue; + + if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE) + break; + + /* Handle dynamic virtual channel intializations */ + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, DRDYNVC_SVC_CHANNEL_NAME)) + { + switch (WTSVirtualChannelManagerGetDrdynvcState(context->vcm)) + { + case DRDYNVC_STATE_NONE: + break; + + case DRDYNVC_STATE_INITIALIZED: + break; + + case DRDYNVC_STATE_READY: + + /* Here is the correct state to start dynamic virtual channels */ + if (sf_peer_audin_running(context) != context->audin_open) + { + if (!sf_peer_audin_running(context)) + sf_peer_audin_start(context); + else + sf_peer_audin_stop(context); + } + +#if defined(CHANNEL_AINPUT_SERVER) + if (sf_peer_ainput_running(context) != context->ainput_open) + { + if (!sf_peer_ainput_running(context)) + sf_peer_ainput_start(context); + else + sf_peer_ainput_stop(context); + } +#endif + + break; + + case DRDYNVC_STATE_FAILED: + default: + break; + } + } + } + + WLog_INFO(TAG, "Client %s disconnected.", client->local ? "(local)" : client->hostname); + + WINPR_ASSERT(client->Disconnect); + client->Disconnect(client); +fail: + freerdp_peer_context_free(client); + freerdp_peer_free(client); + return error; +} + +static BOOL test_peer_accepted(freerdp_listener* instance, freerdp_peer* client) +{ + HANDLE hThread = NULL; + struct server_info* info = NULL; + + WINPR_UNUSED(instance); + + WINPR_ASSERT(instance); + WINPR_ASSERT(client); + + info = instance->info; + client->ContextExtra = info; + + if (!(hThread = CreateThread(NULL, 0, test_peer_mainloop, (void*)client, 0, NULL))) + return FALSE; + + CloseHandle(hThread); + return TRUE; +} + +static void test_server_mainloop(freerdp_listener* instance) +{ + HANDLE handles[32] = { 0 }; + DWORD count = 0; + DWORD status = 0; + + WINPR_ASSERT(instance); + while (1) + { + WINPR_ASSERT(instance->GetEventHandles); + count = instance->GetEventHandles(instance, handles, 32); + + if (0 == count) + { + WLog_ERR(TAG, "Failed to get FreeRDP event handles"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (WAIT_FAILED == status) + { + WLog_ERR(TAG, "select failed"); + break; + } + + WINPR_ASSERT(instance->CheckFileDescriptor); + if (instance->CheckFileDescriptor(instance) != TRUE) + { + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + break; + } + } + + WINPR_ASSERT(instance->Close); + instance->Close(instance); +} + +static const struct +{ + const char spcap[7]; + const char sfast[7]; + const char sport[7]; + const char slocal_only[13]; + const char scert[7]; + const char skey[6]; +} options = { "--pcap=", "--fast", "--port=", "--local-only", "--cert=", "--key=" }; + +WINPR_ATTR_FORMAT_ARG(2, 0) +static void print_entry(FILE* fp, WINPR_FORMAT_ARG const char* fmt, const char* what, size_t size) +{ + char buffer[32] = { 0 }; + strncpy(buffer, what, MIN(size, sizeof(buffer) - 1)); + fprintf(fp, fmt, buffer); +} + +static WINPR_NORETURN(void usage(const char* app, const char* invalid)) +{ + FILE* fp = stdout; + + fprintf(fp, "Invalid argument '%s'\n", invalid); + fprintf(fp, "Usage: %s <arg>[ <arg> ...]\n", app); + fprintf(fp, "Arguments:\n"); + print_entry(fp, "\t%s<pcap file>\n", options.spcap, sizeof(options.spcap)); + print_entry(fp, "\t%s<cert file>\n", options.scert, sizeof(options.scert)); + print_entry(fp, "\t%s<key file>\n", options.skey, sizeof(options.skey)); + print_entry(fp, "\t%s\n", options.sfast, sizeof(options.sfast)); + print_entry(fp, "\t%s<port>\n", options.sport, sizeof(options.sport)); + print_entry(fp, "\t%s\n", options.slocal_only, sizeof(options.slocal_only)); + exit(-1); +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + BOOL started = FALSE; + WSADATA wsaData = { 0 }; + freerdp_listener* instance = NULL; + char* file = NULL; + char name[MAX_PATH] = { 0 }; + long port = 3389; + BOOL localOnly = FALSE; + struct server_info info = { 0 }; + const char* app = argv[0]; + + info.test_dump_rfx_realtime = TRUE; + + errno = 0; + + for (int i = 1; i < argc; i++) + { + char* arg = argv[i]; + + if (strncmp(arg, options.sfast, sizeof(options.sfast)) == 0) + info.test_dump_rfx_realtime = FALSE; + else if (strncmp(arg, options.sport, sizeof(options.sport)) == 0) + { + const char* sport = &arg[sizeof(options.sport)]; + port = strtol(sport, NULL, 10); + + if ((port < 1) || (port > UINT16_MAX) || (errno != 0)) + usage(app, arg); + } + else if (strncmp(arg, options.slocal_only, sizeof(options.slocal_only)) == 0) + localOnly = TRUE; + else if (strncmp(arg, options.spcap, sizeof(options.spcap)) == 0) + { + info.test_pcap_file = &arg[sizeof(options.spcap)]; + if (!winpr_PathFileExists(info.test_pcap_file)) + usage(app, arg); + } + else if (strncmp(arg, options.scert, sizeof(options.scert)) == 0) + { + info.cert = &arg[sizeof(options.scert)]; + if (!winpr_PathFileExists(info.cert)) + usage(app, arg); + } + else if (strncmp(arg, options.skey, sizeof(options.skey)) == 0) + { + info.key = &arg[sizeof(options.skey)]; + if (!winpr_PathFileExists(info.key)) + usage(app, arg); + } + else + usage(app, arg); + } + + WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()); + winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT); + instance = freerdp_listener_new(); + + if (!instance) + return -1; + + if (!info.cert) + info.cert = "server.crt"; + if (!info.key) + info.key = "server.key"; + + instance->info = (void*)&info; + instance->PeerAccepted = test_peer_accepted; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + goto fail; + + /* Open the server socket and start listening. */ + sprintf_s(name, sizeof(name), "tfreerdp-server.%ld", port); + file = GetKnownSubPath(KNOWN_PATH_TEMP, name); + + if (!file) + goto fail; + + if (localOnly) + { + WINPR_ASSERT(instance->OpenLocal); + started = instance->OpenLocal(instance, file); + } + else + { + WINPR_ASSERT(instance->Open); + started = instance->Open(instance, NULL, (UINT16)port); + } + + if (started) + { + /* Entering the server main loop. In a real server the listener can be run in its own + * thread. */ + test_server_mainloop(instance); + } + + rc = 0; +fail: + free(file); + freerdp_listener_free(instance); + WSACleanup(); + return rc; +} diff --git a/server/Sample/sfreerdp.h b/server/Sample/sfreerdp.h new file mode 100644 index 0000000..cba1052 --- /dev/null +++ b/server/Sample/sfreerdp.h @@ -0,0 +1,76 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Sample Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H +#define FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/codec/rfx.h> +#include <freerdp/codec/nsc.h> +#include <freerdp/channels/wtsvc.h> +#if defined(CHANNEL_AINPUT_SERVER) +#include <freerdp/server/ainput.h> +#endif +#if defined(CHANNEL_AUDIN_SERVER) +#include <freerdp/server/audin.h> +#endif +#include <freerdp/server/rdpsnd.h> +#include <freerdp/server/encomsp.h> +#include <freerdp/transport_io.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/image.h> + +struct test_peer_context +{ + rdpContext _p; + + RFX_CONTEXT* rfx_context; + NSC_CONTEXT* nsc_context; + wStream* s; + BYTE* bg_data; + UINT32 icon_x; + UINT32 icon_y; + BOOL activated; + HANDLE event; + HANDLE stopEvent; + HANDLE vcm; + void* debug_channel; + HANDLE debug_channel_thread; +#if defined(CHANNEL_AUDIN_SERVER) + audin_server_context* audin; +#endif + BOOL audin_open; +#if defined(CHANNEL_AINPUT_SERVER) + ainput_server_context* ainput; + BOOL ainput_open; +#endif + UINT32 frame_id; + RdpsndServerContext* rdpsnd; + EncomspServerContext* encomsp; + + rdpTransportIo io; + wImage* image; +}; +typedef struct test_peer_context testPeerContext; + +#endif /* FREERDP_SERVER_SAMPLE_SFREERDP_SERVER_H */ diff --git a/server/Sample/test_icon.bmp b/server/Sample/test_icon.bmp Binary files differnew file mode 100644 index 0000000..08935d4 --- /dev/null +++ b/server/Sample/test_icon.bmp diff --git a/server/Sample/test_icon.jpg b/server/Sample/test_icon.jpg Binary files differnew file mode 100644 index 0000000..758b59e --- /dev/null +++ b/server/Sample/test_icon.jpg diff --git a/server/Sample/test_icon.png b/server/Sample/test_icon.png Binary files differnew file mode 100644 index 0000000..91a4a5a --- /dev/null +++ b/server/Sample/test_icon.png diff --git a/server/Sample/test_icon.ppm b/server/Sample/test_icon.ppm new file mode 100644 index 0000000..bcc4a0e --- /dev/null +++ b/server/Sample/test_icon.ppm @@ -0,0 +1,5572 @@ +P3 +# CREATOR: GIMP PNM Filter Version 1.1 +29 64 +255 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +159 +159 +160 +135 +154 +160 +85 +141 +160 +82 +141 +160 +159 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +112 +148 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +155 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +114 +148 +160 +74 +139 +160 +74 +139 +160 +75 +140 +161 +91 +143 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +136 +154 +160 +76 +140 +160 +74 +139 +160 +74 +139 +160 +75 +140 +161 +137 +154 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +152 +158 +160 +79 +141 +160 +75 +140 +161 +74 +139 +160 +74 +139 +160 +83 +141 +160 +159 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +90 +143 +160 +75 +140 +161 +74 +139 +160 +75 +140 +161 +75 +140 +161 +105 +147 +160 +159 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +125 +151 +160 +74 +139 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +74 +139 +160 +111 +154 +167 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +154 +158 +160 +88 +143 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +138 +160 +99 +170 +189 +134 +171 +180 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +126 +151 +160 +74 +139 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +73 +137 +158 +63 +124 +147 +108 +181 +198 +152 +163 +165 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +80 +141 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +73 +138 +159 +21 +72 +99 +78 +143 +164 +126 +180 +192 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +129 +152 +160 +74 +139 +160 +75 +140 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +73 +138 +159 +25 +77 +104 +12 +60 +88 +105 +176 +194 +145 +167 +172 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +98 +145 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +27 +80 +106 +6 +53 +82 +57 +117 +140 +121 +182 +195 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +83 +141 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +76 +141 +162 +30 +83 +109 +6 +53 +82 +28 +81 +107 +108 +180 +197 +154 +162 +164 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +155 +158 +160 +80 +140 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +74 +139 +160 +74 +138 +159 +73 +138 +159 +75 +140 +161 +37 +92 +118 +2 +48 +78 +19 +69 +96 +100 +171 +189 +147 +165 +170 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +149 +157 +160 +79 +140 +160 +74 +139 +160 +74 +139 +160 +76 +142 +163 +82 +149 +169 +96 +165 +184 +106 +178 +196 +111 +185 +202 +114 +188 +205 +114 +188 +205 +97 +168 +186 +107 +179 +196 +150 +164 +168 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +146 +156 +160 +80 +141 +160 +74 +139 +160 +86 +154 +173 +102 +173 +191 +111 +184 +200 +111 +184 +201 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +111 +184 +201 +110 +183 +200 +113 +179 +195 +138 +170 +177 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +146 +156 +160 +77 +139 +159 +93 +162 +182 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +182 +199 +109 +182 +199 +110 +183 +200 +138 +169 +177 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +146 +156 +159 +99 +165 +182 +112 +185 +202 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +150 +165 +168 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +138 +168 +175 +110 +182 +199 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +182 +199 +107 +180 +197 +107 +168 +183 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +156 +161 +162 +117 +179 +194 +109 +182 +199 +109 +182 +199 +110 +183 +200 +109 +182 +199 +109 +182 +199 +109 +182 +199 +109 +182 +199 +109 +182 +199 +110 +183 +200 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +111 +185 +201 +83 +149 +168 +7 +52 +81 +101 +119 +130 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +144 +167 +172 +110 +183 +200 +110 +182 +199 +110 +183 +200 +109 +182 +199 +61 +125 +148 +47 +91 +116 +58 +95 +118 +43 +88 +112 +64 +128 +151 +108 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +112 +185 +202 +98 +168 +186 +125 +165 +175 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +126 +175 +187 +109 +182 +199 +110 +182 +199 +111 +184 +201 +55 +119 +143 +108 +136 +153 +230 +234 +237 +186 +198 +205 +234 +233 +221 +51 +89 +113 +68 +133 +155 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +109 +182 +199 +110 +183 +200 +123 +176 +188 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +114 +179 +195 +110 +183 +200 +110 +183 +200 +103 +175 +193 +66 +114 +136 +247 +248 +249 +240 +243 +244 +57 +95 +118 +22 +66 +93 +40 +80 +106 +64 +113 +135 +108 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +119 +177 +191 +156 +161 +162 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +110 +182 +199 +110 +183 +200 +110 +183 +200 +100 +172 +190 +106 +143 +161 +249 +250 +250 +251 +252 +252 +132 +155 +170 +51 +89 +113 +145 +165 +178 +79 +121 +142 +108 +181 +198 +110 +183 +200 +109 +182 +199 +110 +184 +200 +113 +187 +203 +103 +175 +193 +104 +177 +194 +117 +180 +195 +154 +162 +164 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +112 +180 +196 +110 +183 +200 +110 +183 +200 +107 +180 +197 +74 +124 +145 +250 +251 +251 +254 +254 +254 +250 +251 +251 +238 +241 +243 +220 +226 +230 +67 +122 +144 +109 +182 +199 +110 +183 +200 +110 +183 +200 +101 +171 +189 +38 +91 +116 +154 +176 +179 +94 +128 +141 +99 +165 +182 +153 +164 +166 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +120 +177 +190 +109 +182 +199 +110 +183 +200 +110 +184 +200 +81 +152 +172 +113 +144 +161 +242 +244 +245 +242 +244 +245 +235 +239 +241 +92 +129 +148 +87 +157 +177 +110 +183 +200 +110 +183 +200 +115 +189 +206 +37 +91 +116 +8 +55 +84 +0 +46 +77 +55 +117 +140 +118 +186 +201 +152 +163 +165 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +134 +171 +180 +109 +182 +199 +109 +181 +198 +109 +182 +199 +113 +187 +203 +88 +159 +179 +83 +140 +160 +105 +152 +169 +76 +134 +155 +88 +159 +179 +112 +185 +202 +109 +182 +199 +110 +183 +200 +110 +183 +200 +109 +182 +199 +81 +147 +167 +61 +122 +144 +113 +187 +204 +113 +179 +195 +153 +163 +165 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +152 +163 +166 +110 +183 +200 +109 +182 +199 +110 +183 +200 +109 +182 +199 +110 +183 +200 +109 +182 +199 +108 +180 +198 +109 +183 +200 +110 +183 +200 +109 +182 +199 +109 +182 +199 +110 +183 +200 +109 +182 +199 +110 +183 +200 +106 +178 +195 +74 +138 +159 +110 +183 +200 +117 +178 +192 +155 +162 +163 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +157 +161 +161 +121 +177 +190 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +95 +164 +183 +77 +142 +163 +110 +183 +200 +123 +175 +188 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +139 +169 +176 +110 +182 +199 +110 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +70 +133 +154 +103 +173 +191 +109 +181 +198 +136 +171 +179 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +159 +160 +160 +115 +179 +194 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +108 +181 +198 +95 +164 +183 +110 +183 +200 +110 +182 +199 +156 +161 +162 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +149 +164 +168 +109 +182 +199 +109 +182 +199 +110 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +111 +183 +200 +109 +182 +199 +136 +170 +177 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +137 +169 +177 +110 +183 +200 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +119 +178 +192 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +157 +161 +162 +120 +176 +189 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +181 +198 +148 +165 +169 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +143 +167 +172 +110 +181 +198 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +181 +198 +109 +182 +199 +126 +175 +186 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +116 +180 +194 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +182 +199 +109 +182 +199 +153 +162 +164 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +132 +172 +181 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +124 +175 +187 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +138 +169 +176 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +115 +165 +178 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +134 +171 +180 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +184 +201 +96 +165 +184 +133 +152 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +119 +177 +190 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +107 +179 +197 +90 +141 +158 +156 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +156 +161 +163 +111 +181 +197 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +82 +149 +169 +127 +151 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +143 +167 +172 +111 +182 +199 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +96 +166 +184 +77 +138 +160 +158 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +131 +172 +182 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +103 +174 +191 +77 +142 +163 +99 +145 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +157 +161 +162 +121 +176 +189 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +82 +149 +168 +74 +140 +160 +126 +152 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +154 +162 +164 +111 +180 +196 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +111 +184 +201 +91 +160 +178 +74 +139 +160 +82 +141 +160 +148 +157 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +145 +166 +171 +128 +173 +184 +122 +176 +189 +129 +174 +184 +160 +160 +160 +160 +160 +160 +160 +160 +160 +144 +166 +171 +109 +182 +199 +110 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +103 +174 +192 +74 +139 +160 +74 +140 +160 +94 +144 +160 +156 +159 +160 +160 +160 +160 +160 +160 +160 +152 +163 +165 +120 +177 +190 +109 +182 +199 +109 +182 +199 +109 +181 +198 +153 +163 +165 +160 +160 +160 +160 +160 +160 +160 +160 +160 +128 +173 +184 +110 +183 +200 +110 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +184 +201 +74 +139 +160 +75 +139 +160 +75 +140 +161 +95 +144 +160 +160 +160 +160 +158 +160 +161 +114 +180 +196 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +151 +164 +167 +160 +160 +160 +160 +160 +160 +160 +160 +160 +115 +179 +194 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +112 +185 +202 +77 +143 +163 +74 +139 +160 +75 +140 +160 +74 +139 +160 +104 +145 +159 +134 +169 +177 +110 +183 +200 +109 +181 +198 +110 +183 +200 +110 +182 +199 +109 +182 +199 +122 +176 +188 +160 +160 +160 +160 +160 +160 +160 +160 +160 +110 +183 +200 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +112 +185 +202 +79 +144 +165 +74 +139 +160 +75 +140 +161 +74 +139 +160 +74 +138 +160 +92 +155 +174 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +109 +181 +198 +110 +183 +200 +160 +160 +160 +160 +160 +160 +160 +160 +160 +110 +183 +200 +109 +181 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +112 +186 +202 +75 +141 +162 +74 +139 +160 +75 +140 +161 +75 +140 +160 +74 +139 +160 +74 +139 +160 +87 +154 +173 +111 +184 +201 +110 +183 +200 +110 +183 +200 +109 +181 +198 +110 +183 +200 +160 +160 +160 +160 +160 +160 +160 +160 +160 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +93 +162 +181 +110 +183 +200 +109 +182 +199 +109 +181 +198 +109 +182 +199 +160 +160 +160 +160 +160 +160 +160 +160 +160 +113 +181 +196 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +104 +176 +194 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +76 +141 +162 +103 +174 +192 +110 +182 +199 +110 +183 +200 +109 +182 +199 +160 +160 +160 +160 +160 +160 +160 +160 +160 +121 +176 +189 +109 +182 +199 +110 +183 +200 +109 +182 +199 +119 +187 +203 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +97 +167 +186 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +80 +145 +165 +111 +185 +202 +109 +182 +199 +143 +167 +173 +160 +160 +160 +160 +160 +160 +160 +160 +160 +133 +172 +181 +109 +182 +199 +110 +182 +199 +109 +182 +199 +140 +198 +211 +111 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +111 +184 +201 +87 +156 +175 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +138 +160 +117 +157 +169 +159 +161 +161 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +147 +165 +169 +110 +183 +200 +109 +182 +199 +110 +183 +200 +161 +208 +219 +113 +184 +201 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +105 +177 +194 +77 +142 +163 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +94 +144 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +154 +162 +164 +114 +181 +196 +109 +182 +199 +109 +182 +200 +181 +218 +227 +114 +185 +201 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +110 +183 +200 +95 +163 +183 +50 +106 +130 +75 +141 +162 +74 +139 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +81 +140 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +158 +160 +161 +125 +175 +187 +109 +182 +199 +108 +182 +199 +201 +228 +235 +116 +186 +202 +109 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +183 +200 +77 +141 +162 +36 +88 +115 +48 +104 +128 +69 +133 +154 +74 +139 +160 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +83 +141 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +139 +169 +176 +110 +182 +199 +107 +180 +197 +204 +230 +236 +137 +196 +210 +108 +182 +199 +110 +183 +200 +110 +183 +200 +110 +183 +200 +109 +182 +199 +110 +184 +200 +46 +101 +126 +38 +90 +116 +48 +103 +128 +61 +120 +143 +69 +133 +154 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +97 +145 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +158 +160 +161 +113 +181 +197 +107 +181 +198 +191 +223 +230 +176 +215 +225 +105 +180 +198 +110 +183 +200 +110 +183 +200 +110 +183 +200 +112 +185 +202 +81 +146 +166 +35 +87 +114 +57 +116 +139 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +127 +152 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +140 +169 +175 +107 +181 +199 +171 +212 +222 +218 +237 +241 +101 +178 +196 +110 +183 +200 +110 +183 +200 +110 +183 +200 +105 +177 +195 +42 +96 +122 +44 +98 +124 +71 +134 +156 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +75 +140 +161 +88 +143 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +120 +176 +189 +144 +200 +213 +251 +252 +252 +108 +182 +199 +109 +182 +199 +109 +182 +199 +109 +182 +199 +53 +110 +135 +37 +90 +116 +46 +101 +127 +72 +135 +157 +74 +139 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +161 +75 +140 +160 +74 +139 +160 +82 +141 +160 +146 +156 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +158 +159 +159 +146 +160 +164 +108 +179 +196 +248 +252 +253 +151 +203 +215 +107 +182 +199 +108 +181 +198 +69 +132 +153 +5 +52 +81 +5 +52 +81 +11 +59 +87 +45 +102 +126 +73 +138 +159 +75 +140 +160 +75 +140 +161 +75 +140 +161 +75 +140 +161 +74 +139 +160 +81 +140 +160 +142 +155 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +141 +147 +150 +70 +98 +115 +41 +77 +99 +41 +95 +119 +120 +157 +173 +157 +187 +198 +67 +131 +154 +46 +103 +127 +4 +50 +79 +6 +53 +82 +6 +54 +83 +6 +53 +82 +3 +50 +79 +28 +81 +108 +52 +111 +135 +61 +123 +145 +77 +132 +151 +94 +139 +154 +116 +146 +157 +154 +158 +159 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +155 +156 +157 +61 +91 +110 +19 +62 +89 +6 +53 +82 +6 +53 +82 +10 +55 +83 +19 +62 +89 +44 +81 +104 +47 +82 +103 +11 +56 +84 +6 +53 +82 +10 +56 +84 +28 +68 +93 +50 +83 +104 +71 +98 +115 +97 +116 +128 +126 +137 +144 +145 +152 +155 +150 +155 +157 +155 +157 +158 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 +160 diff --git a/server/Sample/test_icon.webp b/server/Sample/test_icon.webp Binary files differnew file mode 100644 index 0000000..5a3fa95 --- /dev/null +++ b/server/Sample/test_icon.webp diff --git a/server/Windows/CMakeLists.txt b/server/Windows/CMakeLists.txt new file mode 100644 index 0000000..2744b65 --- /dev/null +++ b/server/Windows/CMakeLists.txt @@ -0,0 +1,128 @@ +# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Windows Server cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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.
+
+set(MODULE_NAME "wfreerdp-server")
+set(MODULE_PREFIX "FREERDP_SERVER_WINDOWS")
+
+include (WarnUnmaintained)
+warn_unmaintained(${MODULE_NAME})
+
+include_directories(.)
+
+set(${MODULE_PREFIX}_SRCS
+ wf_update.c
+ wf_update.h
+ wf_dxgi.c
+ wf_dxgi.h
+ wf_input.c
+ wf_input.h
+ wf_interface.c
+ wf_interface.h
+ wf_mirage.c
+ wf_mirage.h
+ wf_peer.c
+ wf_peer.h
+ wf_settings.c
+ wf_settings.h
+ wf_info.c
+ wf_info.h)
+
+if(CHANNEL_RDPSND AND NOT WITH_RDPSND_DSOUND)
+ set(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_SRCS}
+ wf_rdpsnd.c
+ wf_rdpsnd.h
+ wf_wasapi.c
+ wf_wasapi.h
+ )
+endif()
+
+if(CHANNEL_RDPSND AND WITH_RDPSND_DSOUND)
+ set(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_SRCS}
+ wf_rdpsnd.c
+ wf_rdpsnd.h
+ wf_directsound.c
+ wf_directsound.h
+ )
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+if(WITH_SERVER_INTERFACE)
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+else()
+ set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+endif()
+
+configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+
+if(WITH_SERVER_INTERFACE)
+ add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+ if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+ endif()
+ target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+else()
+ set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} cli/wfreerdp.c cli/wfreerdp.h)
+ add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+ if (WITH_BINARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}")
+ endif()
+endif()
+
+
+if(NOT CMAKE_WINDOWS_VERSION STREQUAL "WINXP")
+ set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} d3d11 dxgi)
+endif()
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} dsound)
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-server freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+if(WITH_SERVER_INTERFACE)
+ install(TARGETS ${MODULE_NAME} COMPONENT libraries
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+ endif()
+else()
+ install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+
+ if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+ endif()
+endif()
+
+if(WITH_SERVER_INTERFACE)
+ add_subdirectory(cli)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Windows")
diff --git a/server/Windows/ModuleOptions.cmake b/server/Windows/ModuleOptions.cmake new file mode 100644 index 0000000..69d7596 --- /dev/null +++ b/server/Windows/ModuleOptions.cmake @@ -0,0 +1,4 @@ + +set(FREERDP_SERVER_NAME "wfreerdp-server") +set(FREERDP_SERVER_PLATFORM "Windows") +set(FREERDP_SERVER_VENDOR "FreeRDP") diff --git a/server/Windows/cli/CMakeLists.txt b/server/Windows/cli/CMakeLists.txt new file mode 100644 index 0000000..e125ac3 --- /dev/null +++ b/server/Windows/cli/CMakeLists.txt @@ -0,0 +1,60 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Windows Server (CLI) cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + +set(MODULE_NAME "wfreerdp-server-cli") +set(OUTPUT_NAME " wfreerdp-server") +set(MODULE_PREFIX "FREERDP_SERVER_WINDOWS_CLI") + +include_directories(..) + +set(${MODULE_PREFIX}_SRCS + wfreerdp.c + wfreerdp.h) + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) +set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) +set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) +set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + +configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + +set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +if (WITH_BINARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME}${FREERDP_API_VERSION}") +else() + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${OUTPUT_NAME}") +endif() + +set(${MODULE_PREFIX}_LIBS wfreerdp-server) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server) + +if (WITH_DEBUG_SYMBOLS AND MSVC) + install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Windows") diff --git a/server/Windows/cli/wfreerdp.c b/server/Windows/cli/wfreerdp.c new file mode 100644 index 0000000..efc1bf5 --- /dev/null +++ b/server/Windows/cli/wfreerdp.c @@ -0,0 +1,171 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <winpr/tchar.h> +#include <winpr/windows.h> + +#include "wf_interface.h" + +#include "wfreerdp.h" + +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +int IDcount = 0; + +BOOL CALLBACK moncb(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) +{ + WLog_DBG(TAG, "%d\t(%ld, %ld), (%ld, %ld)", IDcount, lprcMonitor->left, lprcMonitor->top, + lprcMonitor->right, lprcMonitor->bottom); + IDcount++; + return TRUE; +} + +int main(int argc, char* argv[]) +{ + freerdp_server_warn_unmaintained(argc, argv); + + BOOL screen_selected = FALSE; + int index = 1; + wfServer* server; + server = wfreerdp_server_new(); + set_screen_id(0); + // handle args + errno = 0; + + while (index < argc) + { + // first the args that will cause the program to terminate + if (strcmp("--list-screens", argv[index]) == 0) + { + int width; + int height; + int bpp; + WLog_INFO(TAG, "Detecting screens..."); + WLog_INFO(TAG, "ID\tResolution\t\tName (Interface)"); + + for (int i = 0;; i++) + { + _TCHAR name[128] = { 0 }; + if (get_screen_info(i, name, ARRAYSIZE(name), &width, &height, &bpp) != 0) + { + if ((width * height * bpp) == 0) + continue; + + WLog_INFO(TAG, "%d\t%dx%dx%d\t", i, width, height, bpp); + WLog_INFO(TAG, "%s", name); + } + else + { + break; + } + } + + { + int vscreen_w; + int vscreen_h; + vscreen_w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + vscreen_h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + WLog_INFO(TAG, ""); + EnumDisplayMonitors(NULL, NULL, moncb, 0); + IDcount = 0; + WLog_INFO(TAG, "Virtual Screen = %dx%d", vscreen_w, vscreen_h); + } + + return 0; + } + + if (strcmp("--screen", argv[index]) == 0) + { + UINT32 val; + screen_selected = TRUE; + index++; + + if (index == argc) + { + WLog_INFO(TAG, "missing screen id parameter"); + return 0; + } + + val = strtoul(argv[index], NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return -1; + + set_screen_id(val); + index++; + } + + if (index == argc - 1) + { + UINT32 val = strtoul(argv[index], NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return -1; + + server->port = val; + break; + } + } + + if (screen_selected == FALSE) + { + int width; + int height; + int bpp; + WLog_INFO(TAG, "screen id not provided. attempting to detect..."); + WLog_INFO(TAG, "Detecting screens..."); + WLog_INFO(TAG, "ID\tResolution\t\tName (Interface)"); + + for (int i = 0;; i++) + { + _TCHAR name[128] = { 0 }; + if (get_screen_info(i, name, ARRAYSIZE(name), &width, &height, &bpp) != 0) + { + if ((width * height * bpp) == 0) + continue; + + WLog_INFO(TAG, "%d\t%dx%dx%d\t", i, width, height, bpp); + WLog_INFO(TAG, "%s", name); + set_screen_id(i); + break; + } + else + { + break; + } + } + } + + WLog_INFO(TAG, "Starting server"); + wfreerdp_server_start(server); + WaitForSingleObject(server->thread, INFINITE); + WLog_INFO(TAG, "Stopping server"); + wfreerdp_server_stop(server); + wfreerdp_server_free(server); + return 0; +} diff --git a/server/Windows/cli/wfreerdp.h b/server/Windows/cli/wfreerdp.h new file mode 100644 index 0000000..017106d --- /dev/null +++ b/server/Windows/cli/wfreerdp.h @@ -0,0 +1,25 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_FREERDP_H +#define FREERDP_SERVER_WIN_FREERDP_H + +#include <freerdp/freerdp.h> + +#endif /* FREERDP_SERVER_WIN_FREERDP_H */ diff --git a/server/Windows/wf_directsound.c b/server/Windows/wf_directsound.c new file mode 100644 index 0000000..441397a --- /dev/null +++ b/server/Windows/wf_directsound.c @@ -0,0 +1,219 @@ +#include "wf_directsound.h" +#include "wf_interface.h" +#include "wf_info.h" +#include "wf_rdpsnd.h" + +#include <winpr/windows.h> + +#define INITGUID +#include <initguid.h> +#include <objbase.h> + +#define CINTERFACE 1 +#include <mmsystem.h> +#include <dsound.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +IDirectSoundCapture8* cap; +IDirectSoundCaptureBuffer8* capBuf; +DSCBUFFERDESC dscbd; +DWORD lastPos; +wfPeerContext* latestPeer; + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer) +{ + latestPeer = peer; + return 0; +} + +int wf_directsound_activate(RdpsndServerContext* context) +{ + HRESULT hr; + wfInfo* wfi; + HANDLE hThread; + + LPDIRECTSOUNDCAPTUREBUFFER pDSCB; + + wfi = wf_info_get_instance(); + if (!wfi) + { + WLog_ERR(TAG, "Failed to wfi instance"); + return 1; + } + WLog_DBG(TAG, "RDPSND (direct sound) Activated"); + hr = DirectSoundCaptureCreate8(NULL, &cap, NULL); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to create sound capture device"); + return 1; + } + + WLog_INFO(TAG, "Created sound capture device"); + dscbd.dwSize = sizeof(DSCBUFFERDESC); + dscbd.dwFlags = 0; + dscbd.dwBufferBytes = wfi->agreed_format->nAvgBytesPerSec; + dscbd.dwReserved = 0; + dscbd.lpwfxFormat = wfi->agreed_format; + dscbd.dwFXCount = 0; + dscbd.lpDSCFXDesc = NULL; + + hr = cap->lpVtbl->CreateCaptureBuffer(cap, &dscbd, &pDSCB, NULL); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to create capture buffer"); + } + + WLog_INFO(TAG, "Created capture buffer"); + hr = pDSCB->lpVtbl->QueryInterface(pDSCB, &IID_IDirectSoundCaptureBuffer8, (LPVOID*)&capBuf); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to QI capture buffer"); + } + WLog_INFO(TAG, "Created IDirectSoundCaptureBuffer8"); + pDSCB->lpVtbl->Release(pDSCB); + lastPos = 0; + + if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_directsound_thread, latestPeer, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create direct sound thread"); + return 1; + } + CloseHandle(hThread); + + return 0; +} + +static DWORD WINAPI wf_rdpsnd_directsound_thread(LPVOID lpParam) +{ + HRESULT hr; + DWORD beg = 0; + DWORD end = 0; + DWORD diff, rate; + wfPeerContext* context; + wfInfo* wfi; + + VOID* pbCaptureData = NULL; + DWORD dwCaptureLength = 0; + VOID* pbCaptureData2 = NULL; + DWORD dwCaptureLength2 = 0; + VOID* pbPlayData = NULL; + DWORD dwReadPos = 0; + LONG lLockSize = 0; + + wfi = wf_info_get_instance(); + if (!wfi) + { + WLog_ERR(TAG, "Failed get instance"); + return 1; + } + + context = (wfPeerContext*)lpParam; + rate = 1000 / 24; + WLog_INFO(TAG, "Trying to start capture"); + hr = capBuf->lpVtbl->Start(capBuf, DSCBSTART_LOOPING); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to start capture"); + } + WLog_INFO(TAG, "Capture started"); + + while (1) + { + + end = GetTickCount(); + diff = end - beg; + + if (diff < rate) + { + Sleep(rate - diff); + } + + beg = GetTickCount(); + + if (wf_rdpsnd_lock() > 0) + { + // check for main exit condition + if (wfi->snd_stop == TRUE) + { + wf_rdpsnd_unlock(); + break; + } + + hr = capBuf->lpVtbl->GetCurrentPosition(capBuf, NULL, &dwReadPos); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get read pos"); + wf_rdpsnd_unlock(); + break; + } + + lLockSize = dwReadPos - lastPos; // dscbd.dwBufferBytes; + if (lLockSize < 0) + lLockSize += dscbd.dwBufferBytes; + + // WLog_DBG(TAG, "Last, read, lock = [%"PRIu32", %"PRIu32", %"PRId32"]\n", lastPos, + // dwReadPos, lLockSize); + + if (lLockSize == 0) + { + wf_rdpsnd_unlock(); + continue; + } + + hr = capBuf->lpVtbl->Lock(capBuf, lastPos, lLockSize, &pbCaptureData, &dwCaptureLength, + &pbCaptureData2, &dwCaptureLength2, 0L); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to lock sound capture buffer"); + wf_rdpsnd_unlock(); + break; + } + + // fwrite(pbCaptureData, 1, dwCaptureLength, pFile); + // fwrite(pbCaptureData2, 1, dwCaptureLength2, pFile); + + // FIXME: frames = bytes/(bytespersample * channels) + + context->rdpsnd->SendSamples(context->rdpsnd, pbCaptureData, dwCaptureLength / 4, + (UINT16)(beg & 0xffff)); + context->rdpsnd->SendSamples(context->rdpsnd, pbCaptureData2, dwCaptureLength2 / 4, + (UINT16)(beg & 0xffff)); + + hr = capBuf->lpVtbl->Unlock(capBuf, pbCaptureData, dwCaptureLength, pbCaptureData2, + dwCaptureLength2); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to unlock sound capture buffer"); + wf_rdpsnd_unlock(); + return 0; + } + + // TODO keep track of location in buffer + lastPos += dwCaptureLength; + lastPos %= dscbd.dwBufferBytes; + lastPos += dwCaptureLength2; + lastPos %= dscbd.dwBufferBytes; + + wf_rdpsnd_unlock(); + } + } + + WLog_INFO(TAG, "Trying to stop sound capture"); + hr = capBuf->lpVtbl->Stop(capBuf); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to stop capture"); + } + + WLog_INFO(TAG, "Capture stopped"); + capBuf->lpVtbl->Release(capBuf); + cap->lpVtbl->Release(cap); + + lastPos = 0; + + return 0; +} diff --git a/server/Windows/wf_directsound.h b/server/Windows/wf_directsound.h new file mode 100644 index 0000000..01c1bdd --- /dev/null +++ b/server/Windows/wf_directsound.h @@ -0,0 +1,13 @@ +#ifndef FREERDP_SERVER_WIN_DSOUND_H +#define FREERDP_SERVER_WIN_DSOUND_H + +#include <freerdp/server/rdpsnd.h> +#include "wf_interface.h" + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer); + +int wf_directsound_activate(RdpsndServerContext* context); + +DWORD WINAPI wf_rdpsnd_directsound_thread(LPVOID lpParam); + +#endif /* FREERDP_SERVER_WIN_DSOUND_H */ diff --git a/server/Windows/wf_dxgi.c b/server/Windows/wf_dxgi.c new file mode 100644 index 0000000..899cb55 --- /dev/null +++ b/server/Windows/wf_dxgi.c @@ -0,0 +1,486 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include "wf_interface.h" + +#ifdef WITH_DXGI_1_2 + +#define CINTERFACE + +#include <D3D11.h> +#include <dxgi1_2.h> + +#include <tchar.h> +#include "wf_dxgi.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +/* Driver types supported */ +D3D_DRIVER_TYPE DriverTypes[] = { + D3D_DRIVER_TYPE_HARDWARE, + D3D_DRIVER_TYPE_WARP, + D3D_DRIVER_TYPE_REFERENCE, +}; +UINT NumDriverTypes = ARRAYSIZE(DriverTypes); + +/* Feature levels supported */ +D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 }; + +UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels); + +D3D_FEATURE_LEVEL FeatureLevel; + +ID3D11Device* gDevice = NULL; +ID3D11DeviceContext* gContext = NULL; +IDXGIOutputDuplication* gOutputDuplication = NULL; +ID3D11Texture2D* gAcquiredDesktopImage = NULL; + +IDXGISurface* surf; +ID3D11Texture2D* sStage; + +DXGI_OUTDUPL_FRAME_INFO FrameInfo; + +int wf_dxgi_init(wfInfo* wfi) +{ + gAcquiredDesktopImage = NULL; + + if (wf_dxgi_createDevice(wfi) != 0) + { + return 1; + } + + if (wf_dxgi_getDuplication(wfi) != 0) + { + return 1; + } + + return 0; +} + +int wf_dxgi_createDevice(wfInfo* wfi) +{ + HRESULT status; + UINT DriverTypeIndex; + + for (DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex) + { + status = D3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels, + NumFeatureLevels, D3D11_SDK_VERSION, &gDevice, &FeatureLevel, + &gContext); + if (SUCCEEDED(status)) + break; + + WLog_INFO(TAG, "D3D11CreateDevice returned [%ld] for Driver Type %d", status, + DriverTypes[DriverTypeIndex]); + } + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to create device in InitializeDx"); + return 1; + } + + return 0; +} + +int wf_dxgi_getDuplication(wfInfo* wfi) +{ + HRESULT status; + UINT dTop, i = 0; + DXGI_OUTPUT_DESC desc = { 0 }; + IDXGIOutput* pOutput; + IDXGIDevice* DxgiDevice = NULL; + IDXGIAdapter* DxgiAdapter = NULL; + IDXGIOutput* DxgiOutput = NULL; + IDXGIOutput1* DxgiOutput1 = NULL; + + status = gDevice->lpVtbl->QueryInterface(gDevice, &IID_IDXGIDevice, (void**)&DxgiDevice); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get QI for DXGI Device"); + return 1; + } + + status = DxgiDevice->lpVtbl->GetParent(DxgiDevice, &IID_IDXGIAdapter, (void**)&DxgiAdapter); + DxgiDevice->lpVtbl->Release(DxgiDevice); + DxgiDevice = NULL; + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get parent DXGI Adapter"); + return 1; + } + + pOutput = NULL; + + while (DxgiAdapter->lpVtbl->EnumOutputs(DxgiAdapter, i, &pOutput) != DXGI_ERROR_NOT_FOUND) + { + DXGI_OUTPUT_DESC* pDesc = &desc; + + status = pOutput->lpVtbl->GetDesc(pOutput, pDesc); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get description"); + return 1; + } + + WLog_INFO(TAG, "Output %u: [%s] [%d]", i, pDesc->DeviceName, pDesc->AttachedToDesktop); + + if (pDesc->AttachedToDesktop) + dTop = i; + + pOutput->lpVtbl->Release(pOutput); + ++i; + } + + dTop = wfi->screenID; + + status = DxgiAdapter->lpVtbl->EnumOutputs(DxgiAdapter, dTop, &DxgiOutput); + DxgiAdapter->lpVtbl->Release(DxgiAdapter); + DxgiAdapter = NULL; + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get output"); + return 1; + } + + status = + DxgiOutput->lpVtbl->QueryInterface(DxgiOutput, &IID_IDXGIOutput1, (void**)&DxgiOutput1); + DxgiOutput->lpVtbl->Release(DxgiOutput); + DxgiOutput = NULL; + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get IDXGIOutput1"); + return 1; + } + + status = + DxgiOutput1->lpVtbl->DuplicateOutput(DxgiOutput1, (IUnknown*)gDevice, &gOutputDuplication); + DxgiOutput1->lpVtbl->Release(DxgiOutput1); + DxgiOutput1 = NULL; + + if (FAILED(status)) + { + if (status == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) + { + WLog_ERR( + TAG, + "There is already the maximum number of applications using the Desktop Duplication " + "API running, please close one of those applications and then try again."); + return 1; + } + + WLog_ERR(TAG, "Failed to get duplicate output. Status = %ld", status); + return 1; + } + + return 0; +} + +int wf_dxgi_cleanup(wfInfo* wfi) +{ + if (wfi->framesWaiting > 0) + { + wf_dxgi_releasePixelData(wfi); + } + + if (gAcquiredDesktopImage) + { + gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage); + gAcquiredDesktopImage = NULL; + } + + if (gOutputDuplication) + { + gOutputDuplication->lpVtbl->Release(gOutputDuplication); + gOutputDuplication = NULL; + } + + if (gContext) + { + gContext->lpVtbl->Release(gContext); + gContext = NULL; + } + + if (gDevice) + { + gDevice->lpVtbl->Release(gDevice); + gDevice = NULL; + } + + return 0; +} + +int wf_dxgi_nextFrame(wfInfo* wfi, UINT timeout) +{ + HRESULT status = 0; + UINT i = 0; + UINT DataBufferSize = 0; + BYTE* DataBuffer = NULL; + IDXGIResource* DesktopResource = NULL; + + if (wfi->framesWaiting > 0) + { + wf_dxgi_releasePixelData(wfi); + } + + if (gAcquiredDesktopImage) + { + gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage); + gAcquiredDesktopImage = NULL; + } + + status = gOutputDuplication->lpVtbl->AcquireNextFrame(gOutputDuplication, timeout, &FrameInfo, + &DesktopResource); + + if (status == DXGI_ERROR_WAIT_TIMEOUT) + { + return 1; + } + + if (FAILED(status)) + { + if (status == DXGI_ERROR_ACCESS_LOST) + { + WLog_ERR(TAG, "Failed to acquire next frame with status=%ld", status); + WLog_ERR(TAG, "Trying to reinitialize due to ACCESS LOST..."); + + if (gAcquiredDesktopImage) + { + gAcquiredDesktopImage->lpVtbl->Release(gAcquiredDesktopImage); + gAcquiredDesktopImage = NULL; + } + + if (gOutputDuplication) + { + gOutputDuplication->lpVtbl->Release(gOutputDuplication); + gOutputDuplication = NULL; + } + + wf_dxgi_getDuplication(wfi); + + return 1; + } + else + { + WLog_ERR(TAG, "Failed to acquire next frame with status=%ld", status); + status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to release frame with status=%ld", status); + } + + return 1; + } + } + + status = DesktopResource->lpVtbl->QueryInterface(DesktopResource, &IID_ID3D11Texture2D, + (void**)&gAcquiredDesktopImage); + DesktopResource->lpVtbl->Release(DesktopResource); + DesktopResource = NULL; + + if (FAILED(status)) + { + return 1; + } + + wfi->framesWaiting = FrameInfo.AccumulatedFrames; + + if (FrameInfo.AccumulatedFrames == 0) + { + status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to release frame with status=%ld", status); + } + } + + return 0; +} + +int wf_dxgi_getPixelData(wfInfo* wfi, BYTE** data, int* pitch, RECT* invalid) +{ + HRESULT status; + D3D11_BOX Box; + DXGI_MAPPED_RECT mappedRect; + D3D11_TEXTURE2D_DESC tDesc; + + tDesc.Width = (invalid->right - invalid->left); + tDesc.Height = (invalid->bottom - invalid->top); + tDesc.MipLevels = 1; + tDesc.ArraySize = 1; + tDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + tDesc.SampleDesc.Count = 1; + tDesc.SampleDesc.Quality = 0; + tDesc.Usage = D3D11_USAGE_STAGING; + tDesc.BindFlags = 0; + tDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + tDesc.MiscFlags = 0; + + Box.top = invalid->top; + Box.left = invalid->left; + Box.right = invalid->right; + Box.bottom = invalid->bottom; + Box.front = 0; + Box.back = 1; + + status = gDevice->lpVtbl->CreateTexture2D(gDevice, &tDesc, NULL, &sStage); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to create staging surface"); + exit(1); + return 1; + } + + gContext->lpVtbl->CopySubresourceRegion(gContext, (ID3D11Resource*)sStage, 0, 0, 0, 0, + (ID3D11Resource*)gAcquiredDesktopImage, 0, &Box); + + status = sStage->lpVtbl->QueryInterface(sStage, &IID_IDXGISurface, (void**)&surf); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to QI staging surface"); + exit(1); + return 1; + } + + surf->lpVtbl->Map(surf, &mappedRect, DXGI_MAP_READ); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to map staging surface"); + exit(1); + return 1; + } + + *data = mappedRect.pBits; + *pitch = mappedRect.Pitch; + + return 0; +} + +int wf_dxgi_releasePixelData(wfInfo* wfi) +{ + HRESULT status; + + surf->lpVtbl->Unmap(surf); + surf->lpVtbl->Release(surf); + surf = NULL; + sStage->lpVtbl->Release(sStage); + sStage = NULL; + + status = gOutputDuplication->lpVtbl->ReleaseFrame(gOutputDuplication); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to release frame"); + return 1; + } + + wfi->framesWaiting = 0; + + return 0; +} + +int wf_dxgi_getInvalidRegion(RECT* invalid) +{ + HRESULT status; + UINT dirty; + UINT BufSize; + RECT* pRect; + BYTE* DirtyRects; + UINT DataBufferSize = 0; + BYTE* DataBuffer = NULL; + + if (FrameInfo.AccumulatedFrames == 0) + { + return 1; + } + + if (FrameInfo.TotalMetadataBufferSize) + { + + if (FrameInfo.TotalMetadataBufferSize > DataBufferSize) + { + if (DataBuffer) + { + free(DataBuffer); + DataBuffer = NULL; + } + + DataBuffer = (BYTE*)malloc(FrameInfo.TotalMetadataBufferSize); + + if (!DataBuffer) + { + DataBufferSize = 0; + WLog_ERR(TAG, "Failed to allocate memory for metadata"); + exit(1); + } + + DataBufferSize = FrameInfo.TotalMetadataBufferSize; + } + + BufSize = FrameInfo.TotalMetadataBufferSize; + + status = gOutputDuplication->lpVtbl->GetFrameMoveRects( + gOutputDuplication, BufSize, (DXGI_OUTDUPL_MOVE_RECT*)DataBuffer, &BufSize); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get frame move rects"); + return 1; + } + + DirtyRects = DataBuffer + BufSize; + BufSize = FrameInfo.TotalMetadataBufferSize - BufSize; + + status = gOutputDuplication->lpVtbl->GetFrameDirtyRects(gOutputDuplication, BufSize, + (RECT*)DirtyRects, &BufSize); + + if (FAILED(status)) + { + WLog_ERR(TAG, "Failed to get frame dirty rects"); + return 1; + } + dirty = BufSize / sizeof(RECT); + + pRect = (RECT*)DirtyRects; + + for (UINT i = 0; i < dirty; ++i) + { + UnionRect(invalid, invalid, pRect); + ++pRect; + } + } + + return 0; +} + +#endif diff --git a/server/Windows/wf_dxgi.h b/server/Windows/wf_dxgi.h new file mode 100644 index 0000000..560aec0 --- /dev/null +++ b/server/Windows/wf_dxgi.h @@ -0,0 +1,41 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_DXGI_H +#define FREERDP_SERVER_WIN_DXGI_H + +#include "wf_interface.h" + +int wf_dxgi_init(wfInfo* context); + +int wf_dxgi_createDevice(wfInfo* context); + +int wf_dxgi_getDuplication(wfInfo* context); + +int wf_dxgi_cleanup(wfInfo* context); + +int wf_dxgi_nextFrame(wfInfo* context, UINT timeout); + +int wf_dxgi_getPixelData(wfInfo* context, BYTE** data, int* pitch, RECT* invalid); + +int wf_dxgi_releasePixelData(wfInfo* context); + +int wf_dxgi_getInvalidRegion(RECT* invalid); + +#endif /* FREERDP_SERVER_WIN_DXGI_H */ diff --git a/server/Windows/wf_info.c b/server/Windows/wf_info.c new file mode 100644 index 0000000..7ec754b --- /dev/null +++ b/server/Windows/wf_info.c @@ -0,0 +1,402 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Windows Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <stdlib.h> + +#include <freerdp/build-config.h> + +#include <winpr/tchar.h> +#include <winpr/windows.h> + +#include "wf_info.h" +#include "wf_update.h" +#include "wf_mirage.h" +#include "wf_dxgi.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server" + +static wfInfo* wfInfoInstance = NULL; +static int _IDcount = 0; + +BOOL wf_info_lock(wfInfo* wfi) +{ + DWORD dRes; + dRes = WaitForSingleObject(wfi->mutex, INFINITE); + + switch (dRes) + { + case WAIT_ABANDONED: + case WAIT_OBJECT_0: + return TRUE; + + case WAIT_TIMEOUT: + return FALSE; + + case WAIT_FAILED: + WLog_ERR(TAG, "wf_info_lock failed with 0x%08lX", GetLastError()); + return FALSE; + } + + return FALSE; +} + +BOOL wf_info_try_lock(wfInfo* wfi, DWORD dwMilliseconds) +{ + DWORD dRes; + dRes = WaitForSingleObject(wfi->mutex, dwMilliseconds); + + switch (dRes) + { + case WAIT_ABANDONED: + case WAIT_OBJECT_0: + return TRUE; + + case WAIT_TIMEOUT: + return FALSE; + + case WAIT_FAILED: + WLog_ERR(TAG, "wf_info_try_lock failed with 0x%08lX", GetLastError()); + return FALSE; + } + + return FALSE; +} + +BOOL wf_info_unlock(wfInfo* wfi) +{ + if (!ReleaseMutex(wfi->mutex)) + { + WLog_ERR(TAG, "wf_info_unlock failed with 0x%08lX", GetLastError()); + return FALSE; + } + + return TRUE; +} + +wfInfo* wf_info_init() +{ + wfInfo* wfi; + wfi = (wfInfo*)calloc(1, sizeof(wfInfo)); + + if (wfi != NULL) + { + HKEY hKey; + LONG status; + DWORD dwType; + DWORD dwSize; + DWORD dwValue; + wfi->mutex = CreateMutex(NULL, FALSE, NULL); + + if (wfi->mutex == NULL) + { + WLog_ERR(TAG, "CreateMutex error: %lu", GetLastError()); + free(wfi); + return NULL; + } + + wfi->updateSemaphore = CreateSemaphore(NULL, 0, 32, NULL); + + if (!wfi->updateSemaphore) + { + WLog_ERR(TAG, "CreateSemaphore error: %lu", GetLastError()); + CloseHandle(wfi->mutex); + free(wfi); + return NULL; + } + + wfi->updateThread = CreateThread(NULL, 0, wf_update_thread, wfi, CREATE_SUSPENDED, NULL); + + if (!wfi->updateThread) + { + WLog_ERR(TAG, "Failed to create update thread"); + CloseHandle(wfi->mutex); + CloseHandle(wfi->updateSemaphore); + free(wfi); + return NULL; + } + + wfi->peers = + (freerdp_peer**)calloc(FREERDP_SERVER_WIN_INFO_MAXPEERS, sizeof(freerdp_peer*)); + + if (!wfi->peers) + { + WLog_ERR(TAG, "Failed to allocate memory for peer"); + CloseHandle(wfi->mutex); + CloseHandle(wfi->updateSemaphore); + CloseHandle(wfi->updateThread); + free(wfi); + return NULL; + } + + // Set FPS + wfi->framesPerSecond = FREERDP_SERVER_WIN_INFO_DEFAULT_FPS; + status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("FramesPerSecond"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + wfi->framesPerSecond = dwValue; + } + + RegCloseKey(hKey); + // Set input toggle + wfi->input_disabled = FALSE; + status = + RegOpenKeyExA(HKEY_LOCAL_MACHINE, SERVER_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("DisableInput"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + { + if (dwValue != 0) + wfi->input_disabled = TRUE; + } + } + + RegCloseKey(hKey); + } + + return wfi; +} + +wfInfo* wf_info_get_instance() +{ + if (wfInfoInstance == NULL) + wfInfoInstance = wf_info_init(); + + return wfInfoInstance; +} + +BOOL wf_info_peer_register(wfInfo* wfi, wfPeerContext* context) +{ + int peerId = 0; + + if (!wfi || !context) + return FALSE; + + if (!wf_info_lock(wfi)) + return FALSE; + + if (wfi->peerCount == FREERDP_SERVER_WIN_INFO_MAXPEERS) + goto fail_peer_count; + + context->info = wfi; + + if (!(context->updateEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto fail_update_event; + + // get the offset of the top left corner of selected screen + EnumDisplayMonitors(NULL, NULL, wf_info_monEnumCB, 0); + _IDcount = 0; +#ifdef WITH_DXGI_1_2 + + if (wfi->peerCount == 0) + if (wf_dxgi_init(wfi) != 0) + goto fail_driver_init; + +#else + + if (!wf_mirror_driver_activate(wfi)) + goto fail_driver_init; + +#endif + + // look through the array of peers until an empty slot + for (int i = 0; i < FREERDP_SERVER_WIN_INFO_MAXPEERS; ++i) + { + // empty index will be our peer id + if (wfi->peers[i] == NULL) + { + peerId = i; + break; + } + } + + wfi->peers[peerId] = ((rdpContext*)context)->peer; + wfi->peers[peerId]->pId = peerId; + wfi->peerCount++; + WLog_INFO(TAG, "Registering Peer: id=%d #=%d", peerId, wfi->peerCount); + wf_info_unlock(wfi); + wfreerdp_server_peer_callback_event(peerId, FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_CONNECT); + return TRUE; +fail_driver_init: + CloseHandle(context->updateEvent); + context->updateEvent = NULL; +fail_update_event: +fail_peer_count: + context->socketClose = TRUE; + wf_info_unlock(wfi); + return FALSE; +} + +void wf_info_peer_unregister(wfInfo* wfi, wfPeerContext* context) +{ + if (wf_info_lock(wfi)) + { + int peerId; + peerId = ((rdpContext*)context)->peer->pId; + wfi->peers[peerId] = NULL; + wfi->peerCount--; + CloseHandle(context->updateEvent); + WLog_INFO(TAG, "Unregistering Peer: id=%d, #=%d", peerId, wfi->peerCount); +#ifdef WITH_DXGI_1_2 + + if (wfi->peerCount == 0) + wf_dxgi_cleanup(wfi); + +#endif + wf_info_unlock(wfi); + wfreerdp_server_peer_callback_event(peerId, + FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_DISCONNECT); + } +} + +BOOL wf_info_have_updates(wfInfo* wfi) +{ +#ifdef WITH_DXGI_1_2 + + if (wfi->framesWaiting == 0) + return FALSE; + +#else + + if (wfi->nextUpdate == wfi->lastUpdate) + return FALSE; + +#endif + return TRUE; +} + +void wf_info_update_changes(wfInfo* wfi) +{ +#ifdef WITH_DXGI_1_2 + wf_dxgi_nextFrame(wfi, wfi->framesPerSecond * 1000); +#else + GETCHANGESBUF* buf; + buf = (GETCHANGESBUF*)wfi->changeBuffer; + wfi->nextUpdate = buf->buffer->counter; +#endif +} + +void wf_info_find_invalid_region(wfInfo* wfi) +{ +#ifdef WITH_DXGI_1_2 + wf_dxgi_getInvalidRegion(&wfi->invalid); +#else + GETCHANGESBUF* buf; + buf = (GETCHANGESBUF*)wfi->changeBuffer; + + for (ULONG i = wfi->lastUpdate; i != wfi->nextUpdate; i = (i + 1) % MAXCHANGES_BUF) + { + LPRECT lpR = &buf->buffer->pointrect[i].rect; + + // need to make sure we only get updates from the selected screen + if ((lpR->left >= wfi->servscreen_xoffset) && + (lpR->right <= (wfi->servscreen_xoffset + wfi->servscreen_width)) && + (lpR->top >= wfi->servscreen_yoffset) && + (lpR->bottom <= (wfi->servscreen_yoffset + wfi->servscreen_height))) + { + UnionRect(&wfi->invalid, &wfi->invalid, lpR); + } + else + { + continue; + } + } + +#endif + + if (wfi->invalid.left < 0) + wfi->invalid.left = 0; + + if (wfi->invalid.top < 0) + wfi->invalid.top = 0; + + if (wfi->invalid.right >= wfi->servscreen_width) + wfi->invalid.right = wfi->servscreen_width - 1; + + if (wfi->invalid.bottom >= wfi->servscreen_height) + wfi->invalid.bottom = wfi->servscreen_height - 1; + + // WLog_DBG(TAG, "invalid region: (%"PRId32", %"PRId32"), (%"PRId32", %"PRId32")", + // wfi->invalid.left, wfi->invalid.top, wfi->invalid.right, wfi->invalid.bottom); +} + +void wf_info_clear_invalid_region(wfInfo* wfi) +{ + wfi->lastUpdate = wfi->nextUpdate; + SetRectEmpty(&wfi->invalid); +} + +void wf_info_invalidate_full_screen(wfInfo* wfi) +{ + SetRect(&wfi->invalid, 0, 0, wfi->servscreen_width, wfi->servscreen_height); +} + +BOOL wf_info_have_invalid_region(wfInfo* wfi) +{ + return IsRectEmpty(&wfi->invalid); +} + +void wf_info_getScreenData(wfInfo* wfi, long* width, long* height, BYTE** pBits, int* pitch) +{ + *width = (wfi->invalid.right - wfi->invalid.left); + *height = (wfi->invalid.bottom - wfi->invalid.top); +#ifdef WITH_DXGI_1_2 + wf_dxgi_getPixelData(wfi, pBits, pitch, &wfi->invalid); +#else + { + long offset; + GETCHANGESBUF* changes; + changes = (GETCHANGESBUF*)wfi->changeBuffer; + *width += 1; + *height += 1; + offset = (4 * wfi->invalid.left) + (wfi->invalid.top * wfi->virtscreen_width * 4); + *pBits = ((BYTE*)(changes->Userbuffer)) + offset; + *pitch = wfi->virtscreen_width * 4; + } +#endif +} + +BOOL CALLBACK wf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, + LPARAM dwData) +{ + wfInfo* wfi; + wfi = wf_info_get_instance(); + + if (!wfi) + return FALSE; + + if (_IDcount == wfi->screenID) + { + wfi->servscreen_xoffset = lprcMonitor->left; + wfi->servscreen_yoffset = lprcMonitor->top; + } + + _IDcount++; + return TRUE; +} diff --git a/server/Windows/wf_info.h b/server/Windows/wf_info.h new file mode 100644 index 0000000..82b1781 --- /dev/null +++ b/server/Windows/wf_info.h @@ -0,0 +1,46 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_INFO_H +#define FREERDP_SERVER_WIN_INFO_H + +#include "wf_interface.h" + +#define FREERDP_SERVER_WIN_INFO_DEFAULT_FPS 24 +#define FREERDP_SERVER_WIN_INFO_MAXPEERS 32 + +BOOL wf_info_lock(wfInfo* wfi); +BOOL wf_info_try_lock(wfInfo* wfi, DWORD dwMilliseconds); +BOOL wf_info_unlock(wfInfo* wfi); + +wfInfo* wf_info_get_instance(void); +BOOL wf_info_peer_register(wfInfo* wfi, wfPeerContext* context); +void wf_info_peer_unregister(wfInfo* wfi, wfPeerContext* context); + +BOOL wf_info_have_updates(wfInfo* wfi); +void wf_info_update_changes(wfInfo* wfi); +void wf_info_find_invalid_region(wfInfo* wfi); +void wf_info_clear_invalid_region(wfInfo* wfi); +void wf_info_invalidate_full_screen(wfInfo* wfi); +BOOL wf_info_have_invalid_region(wfInfo* wfi); +void wf_info_getScreenData(wfInfo* wfi, long* width, long* height, BYTE** pBits, int* pitch); +BOOL CALLBACK wf_info_monEnumCB(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, + LPARAM dwData); + +#endif /* FREERDP_SERVER_WIN_INFO_H */ diff --git a/server/Windows/wf_input.c b/server/Windows/wf_input.c new file mode 100644 index 0000000..a9fdd45 --- /dev/null +++ b/server/Windows/wf_input.c @@ -0,0 +1,223 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/windows.h> + +#include "wf_input.h" +#include "wf_info.h" + +BOOL wf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code) +{ + INPUT keyboard_event; + WINPR_UNUSED(input); + keyboard_event.type = INPUT_KEYBOARD; + keyboard_event.ki.wVk = 0; + keyboard_event.ki.wScan = code; + keyboard_event.ki.dwFlags = KEYEVENTF_SCANCODE; + keyboard_event.ki.dwExtraInfo = 0; + keyboard_event.ki.time = 0; + + if (flags & KBD_FLAGS_RELEASE) + keyboard_event.ki.dwFlags |= KEYEVENTF_KEYUP; + + if (flags & KBD_FLAGS_EXTENDED) + keyboard_event.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + + SendInput(1, &keyboard_event, sizeof(INPUT)); + return TRUE; +} + +BOOL wf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) +{ + INPUT keyboard_event; + WINPR_UNUSED(input); + keyboard_event.type = INPUT_KEYBOARD; + keyboard_event.ki.wVk = 0; + keyboard_event.ki.wScan = code; + keyboard_event.ki.dwFlags = KEYEVENTF_UNICODE; + keyboard_event.ki.dwExtraInfo = 0; + keyboard_event.ki.time = 0; + + if (flags & KBD_FLAGS_RELEASE) + keyboard_event.ki.dwFlags |= KEYEVENTF_KEYUP; + + SendInput(1, &keyboard_event, sizeof(INPUT)); + return TRUE; +} + +BOOL wf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + INPUT mouse_event = { 0 }; + float width, height; + WINPR_UNUSED(input); + + WINPR_ASSERT(input); + mouse_event.type = INPUT_MOUSE; + + if (flags & PTR_FLAGS_WHEEL) + { + mouse_event.mi.dwFlags = MOUSEEVENTF_WHEEL; + mouse_event.mi.mouseData = flags & WheelRotationMask; + + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + mouse_event.mi.mouseData *= -1; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + else + { + wfInfo* wfi; + wfi = wf_info_get_instance(); + + if (!wfi) + return FALSE; + + // width and height of primary screen (even in multimon setups + width = (float)GetSystemMetrics(SM_CXSCREEN); + height = (float)GetSystemMetrics(SM_CYSCREEN); + x += wfi->servscreen_xoffset; + y += wfi->servscreen_yoffset; + mouse_event.mi.dx = (LONG)((float)x * (65535.0f / width)); + mouse_event.mi.dy = (LONG)((float)y * (65535.0f / height)); + mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE; + + if (flags & PTR_FLAGS_MOVE) + { + mouse_event.mi.dwFlags |= MOUSEEVENTF_MOVE; + SendInput(1, &mouse_event, sizeof(INPUT)); + } + + mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE; + + if (flags & PTR_FLAGS_BUTTON1) + { + if (flags & PTR_FLAGS_DOWN) + mouse_event.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN; + else + mouse_event.mi.dwFlags |= MOUSEEVENTF_LEFTUP; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + else if (flags & PTR_FLAGS_BUTTON2) + { + if (flags & PTR_FLAGS_DOWN) + mouse_event.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN; + else + mouse_event.mi.dwFlags |= MOUSEEVENTF_RIGHTUP; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + else if (flags & PTR_FLAGS_BUTTON3) + { + if (flags & PTR_FLAGS_DOWN) + mouse_event.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN; + else + mouse_event.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + } + + return TRUE; +} + +BOOL wf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + if ((flags & PTR_XFLAGS_BUTTON1) || (flags & PTR_XFLAGS_BUTTON2)) + { + INPUT mouse_event = { 0 }; + mouse_event.type = INPUT_MOUSE; + + if (flags & PTR_FLAGS_MOVE) + { + float width, height; + wfInfo* wfi; + wfi = wf_info_get_instance(); + + if (!wfi) + return FALSE; + + // width and height of primary screen (even in multimon setups + width = (float)GetSystemMetrics(SM_CXSCREEN); + height = (float)GetSystemMetrics(SM_CYSCREEN); + x += wfi->servscreen_xoffset; + y += wfi->servscreen_yoffset; + mouse_event.mi.dx = (LONG)((float)x * (65535.0f / width)); + mouse_event.mi.dy = (LONG)((float)y * (65535.0f / height)); + mouse_event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + SendInput(1, &mouse_event, sizeof(INPUT)); + } + + mouse_event.mi.dx = mouse_event.mi.dy = mouse_event.mi.dwFlags = 0; + + if (flags & PTR_XFLAGS_DOWN) + mouse_event.mi.dwFlags |= MOUSEEVENTF_XDOWN; + else + mouse_event.mi.dwFlags |= MOUSEEVENTF_XUP; + + if (flags & PTR_XFLAGS_BUTTON1) + mouse_event.mi.mouseData = XBUTTON1; + else if (flags & PTR_XFLAGS_BUTTON2) + mouse_event.mi.mouseData = XBUTTON2; + + SendInput(1, &mouse_event, sizeof(INPUT)); + } + else + { + wf_peer_mouse_event(input, flags, x, y); + } + + return TRUE; +} + +BOOL wf_peer_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT8 code) +{ + WINPR_UNUSED(input); + WINPR_UNUSED(flags); + WINPR_UNUSED(code); + return TRUE; +} + +BOOL wf_peer_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code) +{ + WINPR_UNUSED(input); + WINPR_UNUSED(flags); + WINPR_UNUSED(code); + return TRUE; +} + +BOOL wf_peer_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + WINPR_UNUSED(input); + WINPR_UNUSED(flags); + WINPR_UNUSED(x); + WINPR_UNUSED(y); + return TRUE; +} + +BOOL wf_peer_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + WINPR_UNUSED(input); + WINPR_UNUSED(flags); + WINPR_UNUSED(x); + WINPR_UNUSED(y); + return TRUE; +} diff --git a/server/Windows/wf_input.h b/server/Windows/wf_input.h new file mode 100644 index 0000000..8123652 --- /dev/null +++ b/server/Windows/wf_input.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_INPUT_H +#define FREERDP_SERVER_WIN_INPUT_H + +#include "wf_interface.h" + +BOOL wf_peer_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code); +BOOL wf_peer_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code); +BOOL wf_peer_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); +BOOL wf_peer_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); + +// dummy versions +BOOL wf_peer_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT8 code); +BOOL wf_peer_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code); +BOOL wf_peer_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); +BOOL wf_peer_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y); + +#endif /* FREERDP_SERVER_WIN_INPUT_H */ diff --git a/server/Windows/wf_interface.c b/server/Windows/wf_interface.c new file mode 100644 index 0000000..37923bf --- /dev/null +++ b/server/Windows/wf_interface.c @@ -0,0 +1,341 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <winpr/tchar.h> +#include <winpr/windows.h> +#include <winpr/winsock.h> +#include <winpr/assert.h> + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/constants.h> +#include <freerdp/channels/wtsvc.h> +#include <freerdp/channels/channels.h> +#include <freerdp/build-config.h> + +#include "wf_peer.h" +#include "wf_settings.h" +#include "wf_info.h" + +#include "wf_interface.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\Server" + +static cbCallback cbEvent = NULL; + +int get_screen_info(int id, _TCHAR* name, size_t length, int* width, int* height, int* bpp) +{ + DISPLAY_DEVICE dd = { 0 }; + + dd.cb = sizeof(DISPLAY_DEVICE); + + if (EnumDisplayDevices(NULL, id, &dd, 0) != 0) + { + HDC dc; + + if (name != NULL) + _stprintf_s(name, length, _T("%s (%s)"), dd.DeviceName, dd.DeviceString); + + dc = CreateDC(dd.DeviceName, NULL, NULL, NULL); + *width = GetDeviceCaps(dc, HORZRES); + *height = GetDeviceCaps(dc, VERTRES); + *bpp = GetDeviceCaps(dc, BITSPIXEL); + // ReleaseDC(NULL, dc); + DeleteDC(dc); + } + else + { + return 0; + } + + return 1; +} + +void set_screen_id(int id) +{ + wfInfo* wfi; + + wfi = wf_info_get_instance(); + if (!wfi) + return; + wfi->screenID = id; + + return; +} + +static DWORD WINAPI wf_server_main_loop(LPVOID lpParam) +{ + freerdp_listener* instance; + wfInfo* wfi; + + wfi = wf_info_get_instance(); + if (!wfi) + { + WLog_ERR(TAG, "Failed to get instance"); + return -1; + } + + wfi->force_all_disconnect = FALSE; + + instance = (freerdp_listener*)lpParam; + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->GetEventHandles); + WINPR_ASSERT(instance->CheckFileDescriptor); + + while (wfi->force_all_disconnect == FALSE) + { + DWORD status; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = instance->GetEventHandles(instance, handles, ARRAYSIZE(handles)); + + if (count == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed"); + break; + } + + if (instance->CheckFileDescriptor(instance) != TRUE) + { + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + break; + } + } + + WLog_INFO(TAG, "wf_server_main_loop terminating"); + instance->Close(instance); + + return 0; +} + +BOOL wfreerdp_server_start(wfServer* server) +{ + freerdp_listener* instance; + + server->instance = freerdp_listener_new(); + server->instance->PeerAccepted = wf_peer_accepted; + instance = server->instance; + + wf_settings_read_dword(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("DefaultPort"), &server->port); + + if (!instance->Open(instance, NULL, (UINT16)server->port)) + return FALSE; + + if (!(server->thread = CreateThread(NULL, 0, wf_server_main_loop, (void*)instance, 0, NULL))) + return FALSE; + + return TRUE; +} + +BOOL wfreerdp_server_stop(wfServer* server) +{ + wfInfo* wfi; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + WLog_INFO(TAG, "Stopping server"); + wfi->force_all_disconnect = TRUE; + server->instance->Close(server->instance); + return TRUE; +} + +wfServer* wfreerdp_server_new() +{ + WSADATA wsaData; + wfServer* server; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + return NULL; + + server = (wfServer*)calloc(1, sizeof(wfServer)); + + if (server) + { + server->port = 3389; + } + + WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()); + + cbEvent = NULL; + + return server; +} + +void wfreerdp_server_free(wfServer* server) +{ + free(server); + + WSACleanup(); +} + +BOOL wfreerdp_server_is_running(wfServer* server) +{ + DWORD tStatus; + BOOL bRet; + + bRet = GetExitCodeThread(server->thread, &tStatus); + if (bRet == 0) + { + WLog_ERR(TAG, "Error in call to GetExitCodeThread"); + return FALSE; + } + + if (tStatus == STILL_ACTIVE) + return TRUE; + return FALSE; +} + +UINT32 wfreerdp_server_num_peers() +{ + wfInfo* wfi; + + wfi = wf_info_get_instance(); + if (!wfi) + return -1; + return wfi->peerCount; +} + +UINT32 wfreerdp_server_get_peer_hostname(int pId, wchar_t* dstStr) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return 0; + peer = wfi->peers[pId]; + + if (peer) + { + UINT32 sLen; + + sLen = strnlen_s(peer->hostname, 50); + swprintf(dstStr, 50, L"%hs", peer->hostname); + return sLen; + } + else + { + WLog_WARN(TAG, "nonexistent peer id=%d", pId); + return 0; + } +} + +BOOL wfreerdp_server_peer_is_local(int pId) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + peer = wfi->peers[pId]; + + if (peer) + { + return peer->local; + } + else + { + return FALSE; + } +} + +BOOL wfreerdp_server_peer_is_connected(int pId) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + peer = wfi->peers[pId]; + + if (peer) + { + return peer->connected; + } + else + { + return FALSE; + } +} + +BOOL wfreerdp_server_peer_is_activated(int pId) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + peer = wfi->peers[pId]; + + if (peer) + { + return peer->activated; + } + else + { + return FALSE; + } +} + +BOOL wfreerdp_server_peer_is_authenticated(int pId) +{ + wfInfo* wfi; + freerdp_peer* peer; + + wfi = wf_info_get_instance(); + if (!wfi) + return FALSE; + peer = wfi->peers[pId]; + + if (peer) + { + return peer->authenticated; + } + else + { + return FALSE; + } +} + +void wfreerdp_server_register_callback_event(cbCallback cb) +{ + cbEvent = cb; +} + +void wfreerdp_server_peer_callback_event(int pId, UINT32 eType) +{ + if (cbEvent) + cbEvent(pId, eType); +} diff --git a/server/Windows/wf_interface.h b/server/Windows/wf_interface.h new file mode 100644 index 0000000..5fcbad4 --- /dev/null +++ b/server/Windows/wf_interface.h @@ -0,0 +1,140 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_INTERFACE_H +#define FREERDP_SERVER_WIN_INTERFACE_H + +#include <winpr/windows.h> + +#include <freerdp/api.h> +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> + +#include <freerdp/freerdp.h> +#include <freerdp/codec/rfx.h> + +#include <freerdp/server/rdpsnd.h> + +#if _WIN32_WINNT >= 0x0602 +#define WITH_DXGI_1_2 1 +#endif + +#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_CONNECT 1 +#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_DISCONNECT 2 +#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_ACTIVATE 4 +#define FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_AUTH 8 + +typedef struct wf_info wfInfo; +typedef struct wf_peer_context wfPeerContext; + +struct wf_info +{ + wStream* s; + + // screen and monitor info + int screenID; + int virtscreen_width; + int virtscreen_height; + int servscreen_width; + int servscreen_height; + int servscreen_xoffset; + int servscreen_yoffset; + + int frame_idx; + int bitsPerPixel; + HDC driverDC; + int peerCount; + int activePeerCount; + void* changeBuffer; + int framesPerSecond; + LPTSTR deviceKey; + TCHAR deviceName[32]; + freerdp_peer** peers; + BOOL mirrorDriverActive; + UINT framesWaiting; + + HANDLE snd_mutex; + BOOL snd_stop; + AUDIO_FORMAT* agreed_format; + + RECT invalid; + HANDLE mutex; + BOOL updatePending; + HANDLE updateEvent; + HANDLE updateThread; + HANDLE updateSemaphore; + RFX_CONTEXT* rfx_context; + unsigned long lastUpdate; + unsigned long nextUpdate; + SURFACE_BITS_COMMAND cmd; + + BOOL input_disabled; + BOOL force_all_disconnect; +}; + +struct wf_peer_context +{ + rdpContext _p; + + wfInfo* info; + int frame_idx; + HANDLE updateEvent; + BOOL socketClose; + HANDLE socketEvent; + HANDLE socketThread; + HANDLE socketSemaphore; + + HANDLE vcm; + RdpsndServerContext* rdpsnd; +}; + +struct wf_server +{ + DWORD port; + HANDLE thread; + freerdp_listener* instance; +}; +typedef struct wf_server wfServer; + +typedef void(__stdcall* cbCallback)(int, UINT32); + +FREERDP_API int get_screen_info(int id, _TCHAR* name, size_t length, int* w, int* h, int* b); +FREERDP_API void set_screen_id(int id); + +FREERDP_API BOOL wfreerdp_server_start(wfServer* server); +FREERDP_API BOOL wfreerdp_server_stop(wfServer* server); + +FREERDP_API wfServer* wfreerdp_server_new(void); +FREERDP_API void wfreerdp_server_free(wfServer* server); + +FREERDP_API BOOL wfreerdp_server_is_running(wfServer* server); + +FREERDP_API UINT32 wfreerdp_server_num_peers(void); +FREERDP_API UINT32 wfreerdp_server_get_peer_hostname(int pId, wchar_t* dstStr); +FREERDP_API BOOL wfreerdp_server_peer_is_local(int pId); +FREERDP_API BOOL wfreerdp_server_peer_is_connected(int pId); +FREERDP_API BOOL wfreerdp_server_peer_is_activated(int pId); +FREERDP_API BOOL wfreerdp_server_peer_is_authenticated(int pId); + +FREERDP_API void wfreerdp_server_register_callback_event(cbCallback cb); + +void wfreerdp_server_peer_callback_event(int pId, UINT32 eType); + +#endif /* FREERDP_SERVER_WIN_INTERFACE_H */ diff --git a/server/Windows/wf_mirage.c b/server/Windows/wf_mirage.c new file mode 100644 index 0000000..524ff6e --- /dev/null +++ b/server/Windows/wf_mirage.c @@ -0,0 +1,361 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012-2013 Corey Clayton <can.of.tuna@gmail.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/tchar.h> +#include <winpr/windows.h> + +#include "wf_mirage.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("Windows.mirror") + +#define DEVICE_KEY_PREFIX _T("\\Registry\\Machine\\") +/* +This function will iterate over the loaded display devices until it finds +the mirror device we want to load. If found, it will then copy the registry +key corresponding to the device to the wfi and returns TRUE. Otherwise +the function returns FALSE. +*/ +BOOL wf_mirror_driver_find_display_device(wfInfo* wfi) +{ + BOOL result; + BOOL devFound; + DWORD deviceNumber; + DISPLAY_DEVICE deviceInfo; + devFound = FALSE; + deviceNumber = 0; + deviceInfo.cb = sizeof(deviceInfo); + + while (result = EnumDisplayDevices(NULL, deviceNumber, &deviceInfo, 0)) + { + if (_tcscmp(deviceInfo.DeviceString, _T("Mirage Driver")) == 0) + { + int deviceKeyLength; + int deviceKeyPrefixLength; + deviceKeyPrefixLength = _tcslen(DEVICE_KEY_PREFIX); + + if (_tcsnicmp(deviceInfo.DeviceKey, DEVICE_KEY_PREFIX, deviceKeyPrefixLength) == 0) + { + deviceKeyLength = _tcslen(deviceInfo.DeviceKey) - deviceKeyPrefixLength; + wfi->deviceKey = (LPTSTR)malloc((deviceKeyLength + 1) * sizeof(TCHAR)); + + if (!wfi->deviceKey) + return FALSE; + + _tcsncpy_s(wfi->deviceKey, deviceKeyLength + 1, + &deviceInfo.DeviceKey[deviceKeyPrefixLength], deviceKeyLength); + } + + _tcsncpy_s(wfi->deviceName, 32, deviceInfo.DeviceName, _tcslen(deviceInfo.DeviceName)); + return TRUE; + } + + deviceNumber++; + } + + return FALSE; +} + +/** + * This function will attempt to access the the windows registry using the device + * key stored in the current wfi. It will attempt to read the value of the + * "Attach.ToDesktop" subkey and will return TRUE if the value is already set to + * val. If unable to read the subkey, this function will return FALSE. If the + * subkey is not set to val it will then attempt to set it to val and return TRUE. If + * unsuccessful or an unexpected value is encountered, the function returns + * FALSE. + */ + +BOOL wf_mirror_driver_display_device_attach(wfInfo* wfi, DWORD mode) +{ + HKEY hKey; + LONG status; + DWORD dwType; + DWORD dwSize; + DWORD dwValue; + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, wfi->deviceKey, 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, + &hKey); + + if (status != ERROR_SUCCESS) + { + WLog_DBG(TAG, "Error opening RegKey: status=0x%08lX", status); + + if (status == ERROR_ACCESS_DENIED) + WLog_DBG(TAG, "access denied. Do you have admin privleges?"); + + return FALSE; + } + + dwSize = sizeof(DWORD); + status = RegQueryValueEx(hKey, _T("Attach.ToDesktop"), NULL, &dwType, (BYTE*)&dwValue, &dwSize); + + if (status != ERROR_SUCCESS) + { + WLog_DBG(TAG, "Error querying RegKey: status=0x%08lX", status); + + if (status == ERROR_ACCESS_DENIED) + WLog_DBG(TAG, "access denied. Do you have admin privleges?"); + + return FALSE; + } + + if (dwValue ^ mode) // only if we want to change modes + { + dwValue = mode; + dwSize = sizeof(DWORD); + status = RegSetValueEx(hKey, _T("Attach.ToDesktop"), 0, REG_DWORD, (BYTE*)&dwValue, dwSize); + + if (status != ERROR_SUCCESS) + { + WLog_DBG(TAG, "Error writing registry key: %ld", status); + + if (status == ERROR_ACCESS_DENIED) + WLog_DBG(TAG, "access denied. Do you have admin privleges?"); + + WLog_DBG(TAG, ""); + return FALSE; + } + } + + return TRUE; +} + +void wf_mirror_driver_print_display_change_status(LONG status) +{ + TCHAR disp_change[64]; + + switch (status) + { + case DISP_CHANGE_SUCCESSFUL: + _tcscpy(disp_change, _T("DISP_CHANGE_SUCCESSFUL")); + break; + + case DISP_CHANGE_BADDUALVIEW: + _tcscpy(disp_change, _T("DISP_CHANGE_BADDUALVIEW")); + break; + + case DISP_CHANGE_BADFLAGS: + _tcscpy(disp_change, _T("DISP_CHANGE_BADFLAGS")); + break; + + case DISP_CHANGE_BADMODE: + _tcscpy(disp_change, _T("DISP_CHANGE_BADMODE")); + break; + + case DISP_CHANGE_BADPARAM: + _tcscpy(disp_change, _T("DISP_CHANGE_BADPARAM")); + break; + + case DISP_CHANGE_FAILED: + _tcscpy(disp_change, _T("DISP_CHANGE_FAILED")); + break; + + case DISP_CHANGE_NOTUPDATED: + _tcscpy(disp_change, _T("DISP_CHANGE_NOTUPDATED")); + break; + + case DISP_CHANGE_RESTART: + _tcscpy(disp_change, _T("DISP_CHANGE_RESTART")); + break; + + default: + _tcscpy(disp_change, _T("DISP_CHANGE_UNKNOWN")); + break; + } + + if (status != DISP_CHANGE_SUCCESSFUL) + WLog_ERR(TAG, "ChangeDisplaySettingsEx() failed with %s (%ld)", disp_change, status); + else + WLog_INFO(TAG, "ChangeDisplaySettingsEx() succeeded with %s (%ld)", disp_change, status); +} + +/** + * This function will attempt to apply the currently configured display settings + * in the registry to the display driver. It will return TRUE if successful + * otherwise it returns FALSE. + * If mode is MIRROR_UNLOAD then the the driver will be asked to remove itself. + */ + +BOOL wf_mirror_driver_update(wfInfo* wfi, int mode) +{ + BOOL status; + DWORD* extHdr; + WORD drvExtraSaved; + DEVMODE* deviceMode; + LONG disp_change_status; + DWORD dmf_devmodewext_magic_sig = 0xDF20C0DE; + + if ((mode != MIRROR_LOAD) && (mode != MIRROR_UNLOAD)) + { + WLog_DBG(TAG, "Invalid mirror mode!"); + return FALSE; + } + + deviceMode = (DEVMODE*)malloc(sizeof(DEVMODE) + EXT_DEVMODE_SIZE_MAX); + + if (!deviceMode) + return FALSE; + + deviceMode->dmDriverExtra = 2 * sizeof(DWORD); + extHdr = (DWORD*)((BYTE*)&deviceMode + sizeof(DEVMODE)); + extHdr[0] = dmf_devmodewext_magic_sig; + extHdr[1] = 0; + drvExtraSaved = deviceMode->dmDriverExtra; + memset(deviceMode, 0, sizeof(DEVMODE) + EXT_DEVMODE_SIZE_MAX); + deviceMode->dmSize = sizeof(DEVMODE); + deviceMode->dmDriverExtra = drvExtraSaved; + + if (mode == MIRROR_LOAD) + { + wfi->virtscreen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + wfi->virtscreen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + deviceMode->dmPelsWidth = wfi->virtscreen_width; + deviceMode->dmPelsHeight = wfi->virtscreen_height; + deviceMode->dmBitsPerPel = wfi->bitsPerPixel; + deviceMode->dmPosition.x = wfi->servscreen_xoffset; + deviceMode->dmPosition.y = wfi->servscreen_yoffset; + } + + deviceMode->dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION; + _tcsncpy_s(deviceMode->dmDeviceName, 32, wfi->deviceName, _tcslen(wfi->deviceName)); + disp_change_status = + ChangeDisplaySettingsEx(wfi->deviceName, deviceMode, NULL, CDS_UPDATEREGISTRY, NULL); + status = (disp_change_status == DISP_CHANGE_SUCCESSFUL) ? TRUE : FALSE; + + if (!status) + wf_mirror_driver_print_display_change_status(disp_change_status); + + return status; +} + +BOOL wf_mirror_driver_map_memory(wfInfo* wfi) +{ + int status; + wfi->driverDC = CreateDC(wfi->deviceName, NULL, NULL, NULL); + + if (wfi->driverDC == NULL) + { + WLog_ERR(TAG, "Could not create device driver context!"); + { + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, + NULL); + // Display the error message and exit the process + WLog_ERR(TAG, "CreateDC failed on device [%s] with error %lu: %s", wfi->deviceName, dw, + lpMsgBuf); + LocalFree(lpMsgBuf); + } + return FALSE; + } + + wfi->changeBuffer = calloc(1, sizeof(GETCHANGESBUF)); + + if (!wfi->changeBuffer) + return FALSE; + + status = ExtEscape(wfi->driverDC, dmf_esc_usm_pipe_map, 0, 0, sizeof(GETCHANGESBUF), + (LPSTR)wfi->changeBuffer); + + if (status <= 0) + { + WLog_ERR(TAG, "Failed to map shared memory from the driver! code %d", status); + return FALSE; + } + + return TRUE; +} + +/* Unmap the shared memory and release the DC */ + +BOOL wf_mirror_driver_cleanup(wfInfo* wfi) +{ + int status; + status = ExtEscape(wfi->driverDC, dmf_esc_usm_pipe_unmap, sizeof(GETCHANGESBUF), + (LPSTR)wfi->changeBuffer, 0, 0); + + if (status <= 0) + { + WLog_ERR(TAG, "Failed to unmap shared memory from the driver! code %d", status); + } + + if (wfi->driverDC != NULL) + { + status = DeleteDC(wfi->driverDC); + + if (status == 0) + { + WLog_ERR(TAG, "Failed to release DC!"); + } + } + + free(wfi->changeBuffer); + return TRUE; +} + +BOOL wf_mirror_driver_activate(wfInfo* wfi) +{ + if (!wfi->mirrorDriverActive) + { + WLog_DBG(TAG, "Activating Mirror Driver"); + + if (wf_mirror_driver_find_display_device(wfi) == FALSE) + { + WLog_DBG(TAG, "Could not find dfmirage mirror driver! Is it installed?"); + return FALSE; + } + + if (wf_mirror_driver_display_device_attach(wfi, 1) == FALSE) + { + WLog_DBG(TAG, "Could not attach display device!"); + return FALSE; + } + + if (wf_mirror_driver_update(wfi, MIRROR_LOAD) == FALSE) + { + WLog_DBG(TAG, "could not update system with new display settings!"); + return FALSE; + } + + if (wf_mirror_driver_map_memory(wfi) == FALSE) + { + WLog_DBG(TAG, "Unable to map memory for mirror driver!"); + return FALSE; + } + + wfi->mirrorDriverActive = TRUE; + } + + return TRUE; +} + +void wf_mirror_driver_deactivate(wfInfo* wfi) +{ + if (wfi->mirrorDriverActive) + { + WLog_DBG(TAG, "Deactivating Mirror Driver"); + wf_mirror_driver_cleanup(wfi); + wf_mirror_driver_display_device_attach(wfi, 0); + wf_mirror_driver_update(wfi, MIRROR_UNLOAD); + wfi->mirrorDriverActive = FALSE; + } +} diff --git a/server/Windows/wf_mirage.h b/server/Windows/wf_mirage.h new file mode 100644 index 0000000..a03f0b9 --- /dev/null +++ b/server/Windows/wf_mirage.h @@ -0,0 +1,219 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012-2013 Corey Clayton <can.of.tuna@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_MIRAGE_H +#define FREERDP_SERVER_WIN_MIRAGE_H + +#include "wf_interface.h" + +enum +{ + MIRROR_LOAD = 0, + MIRROR_UNLOAD = 1 +}; + +enum +{ + DMF_ESCAPE_BASE_1_VB = 1030, + DMF_ESCAPE_BASE_2_VB = 1026, + DMF_ESCAPE_BASE_3_VB = 24 +}; + +#ifdef _WIN64 + +#define CLIENT_64BIT 0x8000 + +enum +{ + DMF_ESCAPE_BASE_1 = CLIENT_64BIT | DMF_ESCAPE_BASE_1_VB, + DMF_ESCAPE_BASE_2 = CLIENT_64BIT | DMF_ESCAPE_BASE_2_VB, + DMF_ESCAPE_BASE_3 = CLIENT_64BIT | DMF_ESCAPE_BASE_3_VB, +}; + +#else + +enum +{ + DMF_ESCAPE_BASE_1 = DMF_ESCAPE_BASE_1_VB, + DMF_ESCAPE_BASE_2 = DMF_ESCAPE_BASE_2_VB, + DMF_ESCAPE_BASE_3 = DMF_ESCAPE_BASE_3_VB, +}; + +#endif + +typedef enum +{ + dmf_esc_qry_ver_info = DMF_ESCAPE_BASE_2 + 0, + dmf_esc_usm_pipe_map = DMF_ESCAPE_BASE_1 + 0, + dmf_esc_usm_pipe_unmap = DMF_ESCAPE_BASE_1 + 1, + dmf_esc_test = DMF_ESCAPE_BASE_1 + 20, + dmf_esc_usm_pipe_mapping_test = DMF_ESCAPE_BASE_1 + 21, + dmf_esc_pointer_shape_get = DMF_ESCAPE_BASE_3, + +} dmf_escape; + +#define CLIP_LIMIT 50 +#define MAXCHANGES_BUF 20000 + +typedef enum +{ + dmf_dfo_IGNORE = 0, + dmf_dfo_FROM_SCREEN = 1, + dmf_dfo_FROM_DIB = 2, + dmf_dfo_TO_SCREEN = 3, + dmf_dfo_SCREEN_SCREEN = 11, + dmf_dfo_BLIT = 12, + dmf_dfo_SOLIDFILL = 13, + dmf_dfo_BLEND = 14, + dmf_dfo_TRANS = 15, + dmf_dfo_PLG = 17, + dmf_dfo_TEXTOUT = 18, + dmf_dfo_Ptr_Shape = 19, + dmf_dfo_Ptr_Engage = 48, + dmf_dfo_Ptr_Avert = 49, + dmf_dfn_assert_on = 64, + dmf_dfn_assert_off = 65, +} dmf_UpdEvent; + +#define NOCACHE 1 +#define OLDCACHE 2 +#define NEWCACHE 3 + +typedef struct +{ + ULONG type; + RECT rect; +#ifndef DFMIRAGE_LEAN + RECT origrect; + POINT point; + ULONG color; + ULONG refcolor; +#endif +} CHANGES_RECORD; + +typedef CHANGES_RECORD* PCHANGES_RECORD; + +typedef struct +{ + ULONG counter; + CHANGES_RECORD pointrect[MAXCHANGES_BUF]; +} CHANGES_BUF; + +#define EXT_DEVMODE_SIZE_MAX 3072 +#define DMF_PIPE_SEC_SIZE_DEFAULT ALIGN64K(sizeof(CHANGES_BUF)) + +typedef struct +{ + CHANGES_BUF* buffer; + PVOID Userbuffer; +} GETCHANGESBUF; + +#define dmf_sprb_ERRORMASK 0x07FF +#define dmf_sprb_STRICTSESSION_AFF 0x1FFF + +typedef enum +{ + dmf_sprb_internal_error = 0x0001, + dmf_sprb_miniport_gen_error = 0x0004, + dmf_sprb_memory_alloc_failed = 0x0008, + dmf_sprb_pipe_buff_overflow = 0x0010, + dmf_sprb_pipe_buff_insufficient = 0x0020, + dmf_sprb_pipe_not_ready = 0x0040, + dmf_sprb_gdi_err = 0x0100, + dmf_sprb_owner_died = 0x0400, + dmf_sprb_tgtwnd_gone = 0x0800, + dmf_sprb_pdev_detached = 0x2000, +} dmf_session_prob_status; + +#define DMF_ESC_RET_FAILF 0x80000000 +#define DMF_ESC_RET_SSTMASK 0x0000FFFF +#define DMF_ESC_RET_IMMMASK 0x7FFF0000 + +typedef enum +{ + dmf_escret_generic_ok = 0x00010000, + dmf_escret_bad_state = 0x00100000, + dmf_escret_access_denied = 0x00200000, + dmf_escret_bad_buffer_size = 0x00400000, + dmf_escret_internal_err = 0x00800000, + dmf_escret_out_of_memory = 0x02000000, + dmf_escret_already_connected = 0x04000000, + dmf_escret_oh_boy_too_late = 0x08000000, + dmf_escret_bad_window = 0x10000000, + dmf_escret_drv_ver_higher = 0x20000000, + dmf_escret_drv_ver_lower = 0x40000000, +} dmf_esc_retcode; + +typedef struct +{ + ULONG cbSize; + ULONG app_actual_version; + ULONG display_minreq_version; + ULONG connect_options; +} Esc_dmf_Qvi_IN; + +enum +{ + esc_qvi_prod_name_max = 16, +}; + +#define ESC_QVI_PROD_MIRAGE "MIRAGE" +#define ESC_QVI_PROD_QUASAR "QUASAR" + +typedef struct +{ + ULONG cbSize; + ULONG display_actual_version; + ULONG miniport_actual_version; + ULONG app_minreq_version; + ULONG display_buildno; + ULONG miniport_buildno; + char prod_name[esc_qvi_prod_name_max]; +} Esc_dmf_Qvi_OUT; + +typedef struct +{ + ULONG cbSize; + char* pDstBmBuf; + ULONG nDstBmBufSize; +} Esc_dmf_pointer_shape_get_IN; + +typedef struct +{ + ULONG cbSize; + POINTL BmSize; + char* pMaskBm; + ULONG nMaskBmSize; + char* pColorBm; + ULONG nColorBmSize; + char* pColorBmPal; + ULONG nColorBmPalEntries; +} Esc_dmf_pointer_shape_get_OUT; + +BOOL wf_mirror_driver_find_display_device(wfInfo* wfi); +BOOL wf_mirror_driver_display_device_attach(wfInfo* wfi, DWORD mode); +BOOL wf_mirror_driver_update(wfInfo* wfi, int mode); +BOOL wf_mirror_driver_map_memory(wfInfo* wfi); +BOOL wf_mirror_driver_cleanup(wfInfo* wfi); + +BOOL wf_mirror_driver_activate(wfInfo* wfi); +void wf_mirror_driver_deactivate(wfInfo* wfi); + +#endif /* FREERDP_SERVER_WIN_MIRAGE_H */ diff --git a/server/Windows/wf_peer.c b/server/Windows/wf_peer.c new file mode 100644 index 0000000..4342a3b --- /dev/null +++ b/server/Windows/wf_peer.c @@ -0,0 +1,414 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <winpr/assert.h> +#include <winpr/tchar.h> +#include <winpr/stream.h> +#include <winpr/windows.h> + +#include <freerdp/listener.h> +#include <freerdp/codec/rfx.h> +#include <freerdp/build-config.h> +#include <freerdp/crypto/certificate.h> + +#include "wf_info.h" +#include "wf_input.h" +#include "wf_mirage.h" +#include "wf_update.h" +#include "wf_settings.h" +#include "wf_rdpsnd.h" + +#include "wf_peer.h" +#include <freerdp/peer.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +#define SERVER_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING + +static DWORD WINAPI wf_peer_main_loop(LPVOID lpParam); + +static BOOL wf_peer_context_new(freerdp_peer* client, rdpContext* ctx) +{ + wfPeerContext* context = (wfPeerContext*)ctx; + WINPR_ASSERT(context); + + if (!(context->info = wf_info_get_instance())) + return FALSE; + + context->vcm = WTSOpenServerA((LPSTR)client->context); + + if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE) + return FALSE; + + if (!wf_info_peer_register(context->info, context)) + { + WTSCloseServer(context->vcm); + context->vcm = NULL; + return FALSE; + } + + return TRUE; +} + +static void wf_peer_context_free(freerdp_peer* client, rdpContext* ctx) +{ + wfPeerContext* context = (wfPeerContext*)ctx; + WINPR_ASSERT(context); + + wf_info_peer_unregister(context->info, context); + + if (context->rdpsnd) + { + wf_rdpsnd_lock(); + context->info->snd_stop = TRUE; + rdpsnd_server_context_free(context->rdpsnd); + wf_rdpsnd_unlock(); + } + + WTSCloseServer(context->vcm); +} + +static BOOL wf_peer_init(freerdp_peer* client) +{ + client->ContextSize = sizeof(wfPeerContext); + client->ContextNew = wf_peer_context_new; + client->ContextFree = wf_peer_context_free; + return freerdp_peer_context_new(client); +} + +static BOOL wf_peer_post_connect(freerdp_peer* client) +{ + wfInfo* wfi; + rdpSettings* settings; + wfPeerContext* context; + + WINPR_ASSERT(client); + + context = (wfPeerContext*)client->context; + WINPR_ASSERT(context); + + wfi = context->info; + WINPR_ASSERT(wfi); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + if ((get_screen_info(wfi->screenID, NULL, 0, &wfi->servscreen_width, &wfi->servscreen_height, + &wfi->bitsPerPixel) == 0) || + (wfi->servscreen_width == 0) || (wfi->servscreen_height == 0) || (wfi->bitsPerPixel == 0)) + { + WLog_ERR(TAG, "postconnect: error getting screen info for screen %d", wfi->screenID); + WLog_ERR(TAG, "\t%dx%dx%d", wfi->servscreen_height, wfi->servscreen_width, + wfi->bitsPerPixel); + return FALSE; + } + + if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != wfi->servscreen_width) || + (freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != wfi->servscreen_height)) + { + /* + WLog_DBG(TAG, "Client requested resolution %"PRIu32"x%"PRIu32", but will resize to %dx%d", + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), wfi->servscreen_width, + wfi->servscreen_height); + */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, wfi->servscreen_width) || + !freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, wfi->servscreen_height) || + !freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, wfi->bitsPerPixel)) + return FALSE; + + WINPR_ASSERT(client->context->update); + WINPR_ASSERT(client->context->update->DesktopResize); + client->context->update->DesktopResize(client->context); + } + + if (WTSVirtualChannelManagerIsChannelJoined(context->vcm, "rdpsnd")) + { + wf_peer_rdpsnd_init(context); /* Audio Output */ + } + + return TRUE; +} + +static BOOL wf_peer_activate(freerdp_peer* client) +{ + wfInfo* wfi; + wfPeerContext* context = (wfPeerContext*)client->context; + wfi = context->info; + client->activated = TRUE; + wf_update_peer_activate(wfi, context); + wfreerdp_server_peer_callback_event(((rdpContext*)context)->peer->pId, + FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_ACTIVATE); + return TRUE; +} + +static BOOL wf_peer_logon(freerdp_peer* client, const SEC_WINNT_AUTH_IDENTITY* identity, + BOOL automatic) +{ + wfreerdp_server_peer_callback_event(((rdpContext*)client->context)->peer->pId, + FREERDP_SERVER_WIN_SRV_CALLBACK_EVENT_AUTH); + return TRUE; +} + +static BOOL wf_peer_synchronize_event(rdpInput* input, UINT32 flags) +{ + return TRUE; +} + +BOOL wf_peer_accepted(freerdp_listener* instance, freerdp_peer* client) +{ + HANDLE hThread; + + if (!(hThread = CreateThread(NULL, 0, wf_peer_main_loop, client, 0, NULL))) + return FALSE; + + CloseHandle(hThread); + return TRUE; +} + +static DWORD WINAPI wf_peer_socket_listener(LPVOID lpParam) +{ + wfPeerContext* context; + freerdp_peer* client = (freerdp_peer*)lpParam; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->GetEventHandles); + WINPR_ASSERT(client->CheckFileDescriptor); + + context = (wfPeerContext*)client->context; + WINPR_ASSERT(context); + + while (1) + { + DWORD status; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = client->GetEventHandles(client, handles, ARRAYSIZE(handles)); + + if (count == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed"); + break; + } + + SetEvent(context->socketEvent); + WaitForSingleObject(context->socketSemaphore, INFINITE); + + if (context->socketClose) + break; + } + + return 0; +} + +static BOOL wf_peer_read_settings(freerdp_peer* client) +{ + rdpSettings* settings; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + char* CertificateFile = NULL; + if (!wf_settings_read_string_ascii(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("CertificateFile"), + &(CertificateFile))) + CertificateFile = _strdup("server.crt"); + + rdpCertificate* cert = freerdp_certificate_new_from_file(CertificateFile); + free(CertificateFile); + if (!cert) + return FALSE; + + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1)) + return FALSE; + + char* PrivateKeyFile = NULL; + if (!wf_settings_read_string_ascii(HKEY_LOCAL_MACHINE, SERVER_KEY, _T("PrivateKeyFile"), + &(PrivateKeyFile))) + PrivateKeyFile = _strdup("server.key"); + + rdpPrivateKey* key = freerdp_key_new_from_file(PrivateKeyFile); + free(PrivateKeyFile); + + if (!key) + return FALSE; + + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1)) + return FALSE; + + return TRUE; +} + +DWORD WINAPI wf_peer_main_loop(LPVOID lpParam) +{ + wfInfo* wfi; + DWORD nCount; + DWORD status; + HANDLE handles[32]; + rdpSettings* settings; + wfPeerContext* context; + freerdp_peer* client = (freerdp_peer*)lpParam; + + if (!wf_peer_init(client)) + goto fail_peer_init; + + WINPR_ASSERT(client->context); + + settings = client->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE)) + goto fail_peer_init; + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32)) + goto fail_peer_init; + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, FALSE)) + goto fail_peer_init; + if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, FALSE)) + goto fail_peer_init; + + if (!wf_peer_read_settings(client)) + goto fail_peer_init; + + client->PostConnect = wf_peer_post_connect; + client->Activate = wf_peer_activate; + client->Logon = wf_peer_logon; + + WINPR_ASSERT(client->context->input); + client->context->input->SynchronizeEvent = wf_peer_synchronize_event; + client->context->input->KeyboardEvent = wf_peer_keyboard_event; + client->context->input->UnicodeKeyboardEvent = wf_peer_unicode_keyboard_event; + client->context->input->MouseEvent = wf_peer_mouse_event; + client->context->input->ExtendedMouseEvent = wf_peer_extended_mouse_event; + + WINPR_ASSERT(client->Initialize); + if (!client->Initialize(client)) + goto fail_client_initialize; + + context = (wfPeerContext*)client->context; + + if (context->socketClose) + goto fail_socked_closed; + + wfi = context->info; + + if (wfi->input_disabled) + { + WLog_INFO(TAG, "client input is disabled"); + client->context->input->KeyboardEvent = wf_peer_keyboard_event_dummy; + client->context->input->UnicodeKeyboardEvent = wf_peer_unicode_keyboard_event_dummy; + client->context->input->MouseEvent = wf_peer_mouse_event_dummy; + client->context->input->ExtendedMouseEvent = wf_peer_extended_mouse_event_dummy; + } + + if (!(context->socketEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto fail_socket_event; + + if (!(context->socketSemaphore = CreateSemaphore(NULL, 0, 1, NULL))) + goto fail_socket_semaphore; + + if (!(context->socketThread = CreateThread(NULL, 0, wf_peer_socket_listener, client, 0, NULL))) + goto fail_socket_thread; + + WLog_INFO(TAG, "We've got a client %s", client->local ? "(local)" : client->hostname); + nCount = 0; + handles[nCount++] = context->updateEvent; + handles[nCount++] = context->socketEvent; + + while (1) + { + status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + + if ((status == WAIT_FAILED) || (status == WAIT_TIMEOUT)) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed"); + break; + } + + if (WaitForSingleObject(context->updateEvent, 0) == 0) + { + if (client->activated) + wf_update_peer_send(wfi, context); + + ResetEvent(context->updateEvent); + ReleaseSemaphore(wfi->updateSemaphore, 1, NULL); + } + + if (WaitForSingleObject(context->socketEvent, 0) == 0) + { + if (client->CheckFileDescriptor(client) != TRUE) + { + WLog_ERR(TAG, "Failed to check peer file descriptor"); + context->socketClose = TRUE; + } + + ResetEvent(context->socketEvent); + ReleaseSemaphore(context->socketSemaphore, 1, NULL); + + if (context->socketClose) + break; + } + + // force disconnect + if (wfi->force_all_disconnect == TRUE) + { + WLog_INFO(TAG, "Forcing Disconnect -> "); + break; + } + + /* FIXME: we should wait on this, instead of calling it every time */ + if (WTSVirtualChannelManagerCheckFileDescriptor(context->vcm) != TRUE) + break; + } + + WLog_INFO(TAG, "Client %s disconnected.", client->local ? "(local)" : client->hostname); + + if (WaitForSingleObject(context->updateEvent, 0) == 0) + { + ResetEvent(context->updateEvent); + ReleaseSemaphore(wfi->updateSemaphore, 1, NULL); + } + + wf_update_peer_deactivate(wfi, context); + client->Disconnect(client); +fail_socket_thread: + CloseHandle(context->socketSemaphore); + context->socketSemaphore = NULL; +fail_socket_semaphore: + CloseHandle(context->socketEvent); + context->socketEvent = NULL; +fail_socket_event: +fail_socked_closed: +fail_client_initialize: + freerdp_peer_context_free(client); +fail_peer_init: + freerdp_peer_free(client); + return 0; +} diff --git a/server/Windows/wf_peer.h b/server/Windows/wf_peer.h new file mode 100644 index 0000000..19d823c --- /dev/null +++ b/server/Windows/wf_peer.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_PEER_H +#define FREERDP_SERVER_WIN_PEER_H + +#include "wf_interface.h" + +#include <freerdp/listener.h> + +BOOL wf_peer_accepted(freerdp_listener* instance, freerdp_peer* client); + +#endif /* FREERDP_SERVER_WIN_PEER_H */ diff --git a/server/Windows/wf_rdpsnd.c b/server/Windows/wf_rdpsnd.c new file mode 100644 index 0000000..b313c35 --- /dev/null +++ b/server/Windows/wf_rdpsnd.c @@ -0,0 +1,152 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2013 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> + +#include <winpr/windows.h> +#include <freerdp/server/server-common.h> + +#include "wf_rdpsnd.h" +#include "wf_info.h" + +#ifdef WITH_RDPSND_DSOUND + +#include "wf_directsound.h" + +#else + +#include "wf_wasapi.h" + +#endif + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +static void wf_peer_rdpsnd_activated(RdpsndServerContext* context) +{ + wfInfo* wfi; + wfi = wf_info_get_instance(); + wfi->agreed_format = NULL; + WLog_DBG(TAG, "Client supports the following %d formats:", context->num_client_formats); + + for (size_t i = 0; i < context->num_client_formats; i++) + { + // TODO: improve the way we agree on a format + for (size_t j = 0; j < context->num_server_formats; j++) + { + if ((context->client_formats[i].wFormatTag == context->server_formats[j].wFormatTag) && + (context->client_formats[i].nChannels == context->server_formats[j].nChannels) && + (context->client_formats[i].nSamplesPerSec == + context->server_formats[j].nSamplesPerSec)) + { + WLog_DBG(TAG, "agreed on format!"); + wfi->agreed_format = (AUDIO_FORMAT*)&context->server_formats[j]; + break; + } + } + + if (wfi->agreed_format != NULL) + break; + } + + if (wfi->agreed_format == NULL) + { + WLog_ERR(TAG, "Could not agree on a audio format with the server"); + return; + } + + context->SelectFormat(context, i); + context->SetVolume(context, 0x7FFF, 0x7FFF); +#ifdef WITH_RDPSND_DSOUND + wf_directsound_activate(context); +#else + wf_wasapi_activate(context); +#endif +} + +int wf_rdpsnd_lock() +{ + DWORD dRes; + wfInfo* wfi; + wfi = wf_info_get_instance(); + dRes = WaitForSingleObject(wfi->snd_mutex, INFINITE); + + switch (dRes) + { + case WAIT_ABANDONED: + case WAIT_OBJECT_0: + return TRUE; + break; + + case WAIT_TIMEOUT: + return FALSE; + break; + + case WAIT_FAILED: + WLog_ERR(TAG, "wf_rdpsnd_lock failed with 0x%08lX", GetLastError()); + return -1; + break; + } + + return -1; +} + +int wf_rdpsnd_unlock() +{ + wfInfo* wfi; + wfi = wf_info_get_instance(); + + if (ReleaseMutex(wfi->snd_mutex) == 0) + { + WLog_DBG(TAG, "wf_rdpsnd_unlock failed with 0x%08lX", GetLastError()); + return -1; + } + + return TRUE; +} + +BOOL wf_peer_rdpsnd_init(wfPeerContext* context) +{ + wfInfo* wfi = wf_info_get_instance(); + + if (!wfi) + return FALSE; + + if (!(wfi->snd_mutex = CreateMutex(NULL, FALSE, NULL))) + return FALSE; + + context->rdpsnd = rdpsnd_server_context_new(context->vcm); + context->rdpsnd->rdpcontext = &context->_p; + context->rdpsnd->data = context; + context->rdpsnd->num_server_formats = + server_rdpsnd_get_formats(&context->rdpsnd->server_formats); + + if (context->rdpsnd->num_server_formats > 0) + context->rdpsnd->src_format = &context->rdpsnd->server_formats[0]; + + context->rdpsnd->Activated = wf_peer_rdpsnd_activated; + context->rdpsnd->Initialize(context->rdpsnd, TRUE); + wf_rdpsnd_set_latest_peer(context); + wfi->snd_stop = FALSE; + return TRUE; +} diff --git a/server/Windows/wf_rdpsnd.h b/server/Windows/wf_rdpsnd.h new file mode 100644 index 0000000..88e631d --- /dev/null +++ b/server/Windows/wf_rdpsnd.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server (Audio Output) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_RDPSND_H +#define FREERDP_SERVER_WIN_RDPSND_H + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> +#include <freerdp/server/rdpsnd.h> + +#include "wf_interface.h" + +int wf_rdpsnd_lock(void); +int wf_rdpsnd_unlock(void); +BOOL wf_peer_rdpsnd_init(wfPeerContext* context); + +#endif /* FREERDP_SERVER_WIN_RDPSND_H */ diff --git a/server/Windows/wf_settings.c b/server/Windows/wf_settings.c new file mode 100644 index 0000000..63d2327 --- /dev/null +++ b/server/Windows/wf_settings.c @@ -0,0 +1,102 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/tchar.h> +#include <winpr/windows.h> + +#include "wf_settings.h" + +BOOL wf_settings_read_dword(HKEY key, LPCSTR subkey, LPTSTR name, DWORD* value) +{ + HKEY hKey; + LONG status; + DWORD dwType; + DWORD dwSize; + DWORD dwValue; + + status = RegOpenKeyExA(key, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + dwSize = sizeof(DWORD); + + status = RegQueryValueEx(hKey, name, NULL, &dwType, (BYTE*)&dwValue, &dwSize); + + if (status == ERROR_SUCCESS) + *value = dwValue; + + RegCloseKey(hKey); + + return (status == ERROR_SUCCESS) ? TRUE : FALSE; + } + + return FALSE; +} + +BOOL wf_settings_read_string_ascii(HKEY key, LPCSTR subkey, LPTSTR name, char** value) +{ + HKEY hKey; + int length; + LONG status; + DWORD dwType; + DWORD dwSize; + char* strA; + TCHAR* strX = NULL; + + status = RegOpenKeyExA(key, subkey, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status != ERROR_SUCCESS) + return FALSE; + + status = RegQueryValueEx(hKey, name, NULL, &dwType, NULL, &dwSize); + + if (status == ERROR_SUCCESS) + { + strX = (LPTSTR)malloc(dwSize + sizeof(TCHAR)); + if (!strX) + return FALSE; + status = RegQueryValueEx(hKey, name, NULL, &dwType, (BYTE*)strX, &dwSize); + + if (status != ERROR_SUCCESS) + { + free(strX); + RegCloseKey(hKey); + return FALSE; + } + } + + if (strX) + { +#ifdef UNICODE + length = WideCharToMultiByte(CP_UTF8, 0, strX, lstrlenW(strX), NULL, 0, NULL, NULL); + strA = (char*)malloc(length + 1); + WideCharToMultiByte(CP_UTF8, 0, strX, lstrlenW(strX), strA, length, NULL, NULL); + strA[length] = '\0'; + free(strX); +#else + strA = (char*)strX; +#endif + *value = strA; + return TRUE; + } + + return FALSE; +} diff --git a/server/Windows/wf_settings.h b/server/Windows/wf_settings.h new file mode 100644 index 0000000..40e25aa --- /dev/null +++ b/server/Windows/wf_settings.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_SETTINGS_H +#define FREERDP_SERVER_WIN_SETTINGS_H + +#include "wf_interface.h" + +BOOL wf_settings_read_dword(HKEY key, LPCSTR subkey, LPTSTR name, DWORD* value); +BOOL wf_settings_read_string_ascii(HKEY key, LPCSTR subkey, LPTSTR name, char** value); + +#endif /* FREERDP_SERVER_WIN_SETTINGS_H */ diff --git a/server/Windows/wf_update.c b/server/Windows/wf_update.c new file mode 100644 index 0000000..06d60c9 --- /dev/null +++ b/server/Windows/wf_update.c @@ -0,0 +1,251 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2012 Corey Clayton <can.of.tuna@gmail.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 <freerdp/config.h> + +#include <stdio.h> + +#include <winpr/windows.h> + +#include <freerdp/freerdp.h> +#include <freerdp/listener.h> + +#include "wf_peer.h" +#include "wf_info.h" +#include "wf_mirage.h" + +#include "wf_update.h" + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +DWORD WINAPI wf_update_thread(LPVOID lpParam) +{ + DWORD fps; + wfInfo* wfi; + DWORD beg, end; + DWORD diff, rate; + wfi = (wfInfo*)lpParam; + fps = wfi->framesPerSecond; + rate = 1000 / fps; + + while (1) + { + beg = GetTickCount(); + + if (wf_info_lock(wfi) > 0) + { + if (wfi->activePeerCount > 0) + { + wf_info_update_changes(wfi); + + if (wf_info_have_updates(wfi)) + { + wf_update_encode(wfi); + // WLog_DBG(TAG, "Start of parallel sending"); + int index = 0; + + for (int peerindex = 0; peerindex < wfi->peerCount; peerindex++) + { + for (; index < FREERDP_SERVER_WIN_INFO_MAXPEERS; index++) + { + if (wfi->peers[index] && wfi->peers[index]->activated) + { + // WLog_DBG(TAG, "Setting event for %d of %d", index + 1, + // wfi->activePeerCount); + SetEvent(((wfPeerContext*)wfi->peers[index]->context)->updateEvent); + } + } + } + + for (int index = 0; index < wfi->activePeerCount; index++) + { + // WLog_DBG(TAG, "Waiting for %d of %d", index + 1, wfi->activePeerCount); + // WaitForSingleObject(wfi->updateSemaphore, INFINITE); + WaitForSingleObject(wfi->updateSemaphore, 1000); + } + + // WLog_DBG(TAG, "End of parallel sending"); + wf_info_clear_invalid_region(wfi); + } + } + + wf_info_unlock(wfi); + } + + end = GetTickCount(); + diff = end - beg; + + if (diff < rate) + { + Sleep(rate - diff); + } + } + + // WLog_DBG(TAG, "Exiting Update Thread"); + return 0; +} + +void wf_update_encode(wfInfo* wfi) +{ + RFX_RECT rect; + long height, width; + BYTE* pDataBits = NULL; + int stride; + SURFACE_BITS_COMMAND* cmd; + wf_info_find_invalid_region(wfi); + cmd = &wfi->cmd; + Stream_SetPosition(wfi->s, 0); + wf_info_getScreenData(wfi, &width, &height, &pDataBits, &stride); + rect.x = 0; + rect.y = 0; + rect.width = (UINT16)width; + rect.height = (UINT16)height; + // WLog_DBG(TAG, "x:%"PRId32" y:%"PRId32" w:%ld h:%ld", wfi->invalid.left, wfi->invalid.top, + // width, height); + Stream_Clear(wfi->s); + + if (!(rfx_compose_message(wfi->rfx_context, wfi->s, &rect, 1, pDataBits, width, height, + stride))) + { + return; + } + + wfi->frame_idx = rfx_context_get_frame_idx(wfi->rfx_context); + cmd->destLeft = wfi->invalid.left; + cmd->destTop = wfi->invalid.top; + cmd->destRight = wfi->invalid.left + width; + cmd->destBottom = wfi->invalid.top + height; + cmd->bmp.bpp = 32; + cmd->bmp.codecID = 3; + cmd->bmp.width = width; + cmd->bmp.height = height; + cmd->bmp.bitmapDataLength = Stream_GetPosition(wfi->s); + cmd->bmp.bitmapData = Stream_Buffer(wfi->s); +} + +void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context) +{ + freerdp_peer* client; + + WINPR_ASSERT(wfi); + WINPR_ASSERT(context); + + client = ((rdpContext*)context)->peer; + WINPR_ASSERT(client); + + /* This happens when the RemoteFX encoder state is reset */ + + if (wfi->frame_idx == 1) + context->frame_idx = 0; + + /* + * When a new client connects, it is possible that old frames from + * from a previous encoding state remain. Those frames should be discarded + * as they will cause an error condition in mstsc. + */ + + if ((context->frame_idx + 1) != wfi->frame_idx) + { + /* This frame is meant to be discarded */ + if (context->frame_idx == 0) + return; + + /* This is an unexpected error condition */ + WLog_DBG(TAG, "Unexpected Frame Index: Actual: %d Expected: %d", wfi->frame_idx, + context->frame_idx + 1); + } + + WINPR_ASSERT(client->context); + WINPR_ASSERT(client->context->settings); + WINPR_ASSERT(client->context->update); + WINPR_ASSERT(client->context->update->SurfaceBits); + + wfi->cmd.bmp.codecID = + freerdp_settings_get_uint32(client->context->settings, FreeRDP_RemoteFxCodecId); + client->context->update->SurfaceBits(client->context, &wfi->cmd); + context->frame_idx++; +} + +void wf_update_encoder_reset(wfInfo* wfi) +{ + if (wf_info_lock(wfi) > 0) + { + WLog_DBG(TAG, "Resetting encoder"); + + if (wfi->rfx_context) + { + rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height); + } + else + { + /* TODO: pass ThreadingFlags somehow */ + wfi->rfx_context = rfx_context_new(TRUE); + rfx_context_set_mode(wfi->rfx_context, RLGR3); + rfx_context_reset(wfi->rfx_context, wfi->servscreen_width, wfi->servscreen_height); + rfx_context_set_pixel_format(wfi->rfx_context, PIXEL_FORMAT_BGRA32); + wfi->s = Stream_New(NULL, 0xFFFF); + } + + wf_info_invalidate_full_screen(wfi); + wf_info_unlock(wfi); + } +} + +void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context) +{ + if (wf_info_lock(wfi) > 0) + { + if (wfi->activePeerCount < 1) + { +#ifndef WITH_DXGI_1_2 + wf_mirror_driver_activate(wfi); +#endif + ResumeThread(wfi->updateThread); + } + + wf_update_encoder_reset(wfi); + wfi->activePeerCount++; + WLog_DBG(TAG, "Activating Peer Updates: %d", wfi->activePeerCount); + wf_info_unlock(wfi); + } +} + +void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context) +{ + if (wf_info_lock(wfi) > 0) + { + freerdp_peer* client = ((rdpContext*)context)->peer; + + if (client->activated) + { + if (wfi->activePeerCount <= 1) + { + wf_mirror_driver_deactivate(wfi); + } + + client->activated = FALSE; + wfi->activePeerCount--; + WLog_DBG(TAG, "Deactivating Peer Updates: %d", wfi->activePeerCount); + } + + wf_info_unlock(wfi); + } +} diff --git a/server/Windows/wf_update.h b/server/Windows/wf_update.h new file mode 100644 index 0000000..47553af --- /dev/null +++ b/server/Windows/wf_update.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Windows Server + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_WIN_UPDATE_H +#define FREERDP_SERVER_WIN_UPDATE_H + +#include "wf_interface.h" + +void wf_update_encode(wfInfo* wfi); +void wf_update_send(wfInfo* wfi); + +DWORD WINAPI wf_update_thread(LPVOID lpParam); + +void wf_update_begin(wfInfo* wfi); +void wf_update_peer_send(wfInfo* wfi, wfPeerContext* context); +void wf_update_end(wfInfo* wfi); + +void wf_update_peer_activate(wfInfo* wfi, wfPeerContext* context); +void wf_update_peer_deactivate(wfInfo* wfi, wfPeerContext* context); + +#endif /* FREERDP_SERVER_WIN_UPDATE_H */ diff --git a/server/Windows/wf_wasapi.c b/server/Windows/wf_wasapi.c new file mode 100644 index 0000000..3925f99 --- /dev/null +++ b/server/Windows/wf_wasapi.c @@ -0,0 +1,333 @@ + +#include "wf_wasapi.h" +#include "wf_info.h" + +#include <initguid.h> +#include <mmdeviceapi.h> +#include <functiondiscoverykeys_devpkey.h> +#include <audioclient.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +//#define REFTIMES_PER_SEC 10000000 +//#define REFTIMES_PER_MILLISEC 10000 + +#define REFTIMES_PER_SEC 100000 +#define REFTIMES_PER_MILLISEC 100 + +//#define REFTIMES_PER_SEC 50000 +//#define REFTIMES_PER_MILLISEC 50 + +#ifndef __MINGW32__ +DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, + 0x91, 0x69, 0x2E); +DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, + 0x17, 0xE6); +DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, + 0xb2); +DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, + 0xd3, 0x17); +#endif + +LPWSTR devStr = NULL; +wfPeerContext* latestPeer = NULL; + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer) +{ + latestPeer = peer; + return 0; +} + +int wf_wasapi_activate(RdpsndServerContext* context) +{ + wchar_t* pattern = L"Stereo Mix"; + HANDLE hThread; + + wf_wasapi_get_device_string(pattern, &devStr); + + if (devStr == NULL) + { + WLog_ERR(TAG, "Failed to match for output device! Disabling rdpsnd."); + return 1; + } + + WLog_DBG(TAG, "RDPSND (WASAPI) Activated"); + if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_wasapi_thread, latestPeer, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed"); + return 1; + } + CloseHandle(hThread); + + return 0; +} + +int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr) +{ + HRESULT hr; + IMMDeviceEnumerator* pEnumerator = NULL; + IMMDeviceCollection* pCollection = NULL; + IMMDevice* pEndpoint = NULL; + IPropertyStore* pProps = NULL; + LPWSTR pwszID = NULL; + unsigned int count; + + CoInitialize(NULL); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, + (void**)&pEnumerator); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate device enumerator"); + exit(1); + } + + hr = pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eCapture, DEVICE_STATE_ACTIVE, + &pCollection); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to create endpoint collection"); + exit(1); + } + + pCollection->lpVtbl->GetCount(pCollection, &count); + WLog_INFO(TAG, "Num endpoints: %u", count); + + if (count == 0) + { + WLog_ERR(TAG, "No endpoints!"); + exit(1); + } + + for (unsigned int i = 0; i < count; ++i) + { + PROPVARIANT nameVar; + PropVariantInit(&nameVar); + + hr = pCollection->lpVtbl->Item(pCollection, i, &pEndpoint); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get endpoint %u", i); + exit(1); + } + + hr = pEndpoint->lpVtbl->GetId(pEndpoint, &pwszID); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get endpoint ID"); + exit(1); + } + + hr = pEndpoint->lpVtbl->OpenPropertyStore(pEndpoint, STGM_READ, &pProps); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to open property store"); + exit(1); + } + + hr = pProps->lpVtbl->GetValue(pProps, &PKEY_Device_FriendlyName, &nameVar); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get device friendly name"); + exit(1); + } + + // do this a more reliable way + if (wcscmp(pattern, nameVar.pwszVal) < 0) + { + unsigned int devStrLen; + WLog_INFO(TAG, "Using sound ouput endpoint: [%s] (%s)", nameVar.pwszVal, pwszID); + // WLog_INFO(TAG, "matched %d characters", wcscmp(pattern, nameVar.pwszVal); + devStrLen = wcslen(pwszID); + *deviceStr = (LPWSTR)calloc(devStrLen + 1, 2); + if (!deviceStr) + return -1; + wcscpy_s(*deviceStr, devStrLen + 1, pwszID); + } + CoTaskMemFree(pwszID); + pwszID = NULL; + PropVariantClear(&nameVar); + + pProps->lpVtbl->Release(pProps); + pProps = NULL; + + pEndpoint->lpVtbl->Release(pEndpoint); + pEndpoint = NULL; + } + + pCollection->lpVtbl->Release(pCollection); + pCollection = NULL; + + pEnumerator->lpVtbl->Release(pEnumerator); + pEnumerator = NULL; + CoUninitialize(); + + return 0; +} + +DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam) +{ + IMMDeviceEnumerator* pEnumerator = NULL; + IMMDevice* pDevice = NULL; + IAudioClient* pAudioClient = NULL; + IAudioCaptureClient* pCaptureClient = NULL; + WAVEFORMATEX* pwfx = NULL; + HRESULT hr; + REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; + REFERENCE_TIME hnsActualDuration; + UINT32 bufferFrameCount; + UINT32 numFramesAvailable; + UINT32 packetLength = 0; + UINT32 dCount = 0; + BYTE* pData; + + wfPeerContext* context; + wfInfo* wfi; + + wfi = wf_info_get_instance(); + context = (wfPeerContext*)lpParam; + + CoInitialize(NULL); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, + (void**)&pEnumerator); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate device enumerator"); + exit(1); + } + + hr = pEnumerator->lpVtbl->GetDevice(pEnumerator, devStr, &pDevice); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate get device"); + exit(1); + } + + hr = pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, + (void**)&pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to activate audio client"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetMixFormat(pAudioClient, &pwfx); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get mix format"); + exit(1); + } + + pwfx->wFormatTag = wfi->agreed_format->wFormatTag; + pwfx->nChannels = wfi->agreed_format->nChannels; + pwfx->nSamplesPerSec = wfi->agreed_format->nSamplesPerSec; + pwfx->nAvgBytesPerSec = wfi->agreed_format->nAvgBytesPerSec; + pwfx->nBlockAlign = wfi->agreed_format->nBlockAlign; + pwfx->wBitsPerSample = wfi->agreed_format->wBitsPerSample; + pwfx->cbSize = wfi->agreed_format->cbSize; + + hr = pAudioClient->lpVtbl->Initialize(pAudioClient, AUDCLNT_SHAREMODE_SHARED, 0, + hnsRequestedDuration, 0, pwfx, NULL); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to initialize the audio client"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get buffer size"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioCaptureClient, + (void**)&pCaptureClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get the capture client"); + exit(1); + } + + hnsActualDuration = (UINT32)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec; + + hr = pAudioClient->lpVtbl->Start(pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to start capture"); + exit(1); + } + + dCount = 0; + + while (wfi->snd_stop == FALSE) + { + DWORD flags; + + Sleep(hnsActualDuration / REFTIMES_PER_MILLISEC / 2); + + hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get packet length"); + exit(1); + } + + while (packetLength != 0) + { + hr = pCaptureClient->lpVtbl->GetBuffer(pCaptureClient, &pData, &numFramesAvailable, + &flags, NULL, NULL); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get buffer"); + exit(1); + } + + // Here we are writing the audio data + // not sure if this flag is ever set by the system; msdn is not clear about it + if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT)) + context->rdpsnd->SendSamples(context->rdpsnd, pData, packetLength, + (UINT16)(GetTickCount() & 0xffff)); + + hr = pCaptureClient->lpVtbl->ReleaseBuffer(pCaptureClient, numFramesAvailable); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to release buffer"); + exit(1); + } + + hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get packet length"); + exit(1); + } + } + } + + pAudioClient->lpVtbl->Stop(pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to stop audio client"); + exit(1); + } + + CoTaskMemFree(pwfx); + + if (pEnumerator != NULL) + pEnumerator->lpVtbl->Release(pEnumerator); + + if (pDevice != NULL) + pDevice->lpVtbl->Release(pDevice); + + if (pAudioClient != NULL) + pAudioClient->lpVtbl->Release(pAudioClient); + + if (pCaptureClient != NULL) + pCaptureClient->lpVtbl->Release(pCaptureClient); + + CoUninitialize(); + + return 0; +} diff --git a/server/Windows/wf_wasapi.h b/server/Windows/wf_wasapi.h new file mode 100644 index 0000000..da9c7dc --- /dev/null +++ b/server/Windows/wf_wasapi.h @@ -0,0 +1,15 @@ +#ifndef FREERDP_SERVER_WIN_WASAPI_H +#define FREERDP_SERVER_WIN_WASAPI_H + +#include <freerdp/server/rdpsnd.h> +#include "wf_interface.h" + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer); + +int wf_wasapi_activate(RdpsndServerContext* context); + +int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr); + +DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam); + +#endif /* FREERDP_SERVER_WIN_WASAPI_H */ diff --git a/server/common/CMakeLists.txt b/server/common/CMakeLists.txt new file mode 100644 index 0000000..5552688 --- /dev/null +++ b/server/common/CMakeLists.txt @@ -0,0 +1,77 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Server Common +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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. + +set(MODULE_NAME "freerdp-server") +set(MODULE_PREFIX "FREERDP_SERVER") + +# Policy CMP0022: INTERFACE_LINK_LIBRARIES defines the link +# interface. Run "cmake --help-policy CMP0022" for policy details. Use the +# cmake_policy command to set the policy and suppress this warning. +if(POLICY CMP0022) + cmake_policy(SET CMP0022 NEW) +endif() + +set(${MODULE_PREFIX}_SRCS + server.c) + +foreach(FREERDP_CHANNELS_SERVER_SRC ${FREERDP_CHANNELS_SERVER_SRCS}) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} "${FREERDP_CHANNELS_SERVER_SRC}") +endforeach() + +if(MSVC) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS}) +endif() + + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" ) + + configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set (${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION}) + +if (WITH_LIBRARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION}) +endif() + +target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>) +target_link_libraries(${MODULE_NAME} PRIVATE ${FREERDP_CHANNELS_SERVER_LIBS}) +target_link_libraries(${MODULE_NAME} PUBLIC winpr freerdp) + +install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT FreeRDP-ServerTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS) + get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Common") diff --git a/server/common/server.c b/server/common/server.c new file mode 100644 index 0000000..62f126f --- /dev/null +++ b/server/common/server.c @@ -0,0 +1,236 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Server Common + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/wtypes.h> +#include <freerdp/codec/audio.h> +#include <freerdp/codec/dsp.h> + +#include <freerdp/server/server-common.h> +#include <freerdp/log.h> + +#define TAG FREERDP_TAG("server.common") + +size_t server_audin_get_formats(AUDIO_FORMAT** dst_formats) +{ + /* Default supported audio formats */ + BYTE adpcm_data_7[] = { 0xf4, 0x07, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff }; + BYTE adpcm_data_3[] = { 0xf4, 0x03, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff }; + BYTE adpcm_data_1[] = { 0xf4, 0x01, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, + 0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0xcc, 0x01, 0x30, 0xff, 0x88, 0x01, 0x18, 0xff }; + BYTE adpcm_dvi_data_7[] = { 0xf9, 0x07 }; + BYTE adpcm_dvi_data_3[] = { 0xf9, 0x03 }; + BYTE adpcm_dvi_data_1[] = { 0xf9, 0x01 }; + BYTE gsm610_data[] = { 0x40, 0x01 }; + const AUDIO_FORMAT default_supported_audio_formats[] = { + /* Formats sent by windows 10 server */ + { WAVE_FORMAT_AAC_MS, 2, 44100, 24000, 4, 16, 0, NULL }, + { WAVE_FORMAT_AAC_MS, 2, 44100, 20000, 4, 16, 0, NULL }, + { WAVE_FORMAT_AAC_MS, 2, 44100, 16000, 4, 16, 0, NULL }, + { WAVE_FORMAT_AAC_MS, 2, 44100, 12000, 4, 16, 0, NULL }, + { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL }, + { WAVE_FORMAT_ADPCM, 2, 44100, 44359, 2048, 4, 32, adpcm_data_7 }, + { WAVE_FORMAT_DVI_ADPCM, 2, 44100, 44251, 2048, 4, 2, adpcm_dvi_data_7 }, + { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL }, + { WAVE_FORMAT_ADPCM, 2, 22050, 22311, 1024, 4, 32, adpcm_data_3 }, + { WAVE_FORMAT_DVI_ADPCM, 2, 22050, 22201, 1024, 4, 2, adpcm_dvi_data_3 }, + { WAVE_FORMAT_ADPCM, 1, 44100, 22179, 1024, 4, 32, adpcm_data_7 }, + { WAVE_FORMAT_DVI_ADPCM, 1, 44100, 22125, 1024, 4, 2, adpcm_dvi_data_7 }, + { WAVE_FORMAT_ADPCM, 2, 11025, 11289, 512, 4, 32, adpcm_data_1 }, + { WAVE_FORMAT_DVI_ADPCM, 2, 11025, 11177, 512, 4, 2, adpcm_dvi_data_1 }, + { WAVE_FORMAT_ADPCM, 1, 22050, 11155, 512, 4, 32, adpcm_data_3 }, + { WAVE_FORMAT_DVI_ADPCM, 1, 22050, 11100, 512, 4, 2, adpcm_dvi_data_3 }, + { WAVE_FORMAT_GSM610, 1, 44100, 8957, 65, 0, 2, gsm610_data }, + { WAVE_FORMAT_ADPCM, 2, 8000, 8192, 512, 4, 32, adpcm_data_1 }, + { WAVE_FORMAT_DVI_ADPCM, 2, 8000, 8110, 512, 4, 2, adpcm_dvi_data_1 }, + { WAVE_FORMAT_ADPCM, 1, 11025, 5644, 256, 4, 32, adpcm_data_1 }, + { WAVE_FORMAT_DVI_ADPCM, 1, 11025, 5588, 256, 4, 2, adpcm_dvi_data_1 }, + { WAVE_FORMAT_GSM610, 1, 22050, 4478, 65, 0, 2, gsm610_data }, + { WAVE_FORMAT_ADPCM, 1, 8000, 4096, 256, 4, 32, adpcm_data_1 }, + { WAVE_FORMAT_DVI_ADPCM, 1, 8000, 4055, 256, 4, 2, adpcm_dvi_data_1 }, + { WAVE_FORMAT_GSM610, 1, 11025, 2239, 65, 0, 2, gsm610_data }, + { WAVE_FORMAT_GSM610, 1, 8000, 1625, 65, 0, 2, gsm610_data }, + /* Formats added for others */ + + { WAVE_FORMAT_MSG723, 2, 44100, 0, 4, 16, 0, NULL }, + { WAVE_FORMAT_MSG723, 2, 22050, 0, 4, 16, 0, NULL }, + { WAVE_FORMAT_MSG723, 1, 44100, 0, 4, 16, 0, NULL }, + { WAVE_FORMAT_MSG723, 1, 22050, 0, 4, 16, 0, NULL }, + { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL }, + { WAVE_FORMAT_PCM, 2, 22050, 88200, 4, 16, 0, NULL }, + { WAVE_FORMAT_PCM, 1, 44100, 88200, 4, 16, 0, NULL }, + { WAVE_FORMAT_PCM, 1, 22050, 44100, 4, 16, 0, NULL }, + { WAVE_FORMAT_MULAW, 2, 44100, 88200, 4, 16, 0, NULL }, + { WAVE_FORMAT_MULAW, 2, 22050, 44100, 4, 16, 0, NULL }, + { WAVE_FORMAT_MULAW, 1, 44100, 44100, 4, 16, 0, NULL }, + { WAVE_FORMAT_MULAW, 1, 22050, 22050, 4, 16, 0, NULL }, + { WAVE_FORMAT_ALAW, 2, 44100, 88200, 2, 8, 0, NULL }, + { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL }, + { WAVE_FORMAT_ALAW, 1, 44100, 44100, 2, 8, 0, NULL }, + { WAVE_FORMAT_ALAW, 1, 22050, 22050, 2, 8, 0, NULL } + }; + const size_t nrDefaultFormatsMax = ARRAYSIZE(default_supported_audio_formats); + size_t nr_formats = 0; + AUDIO_FORMAT* formats = audio_formats_new(nrDefaultFormatsMax); + + if (!dst_formats) + goto fail; + + *dst_formats = NULL; + + if (!formats) + goto fail; + + for (size_t x = 0; x < nrDefaultFormatsMax; x++) + { + const AUDIO_FORMAT* format = &default_supported_audio_formats[x]; + + if (freerdp_dsp_supports_format(format, FALSE)) + { + AUDIO_FORMAT* dst = &formats[nr_formats++]; + + if (!audio_format_copy(format, dst)) + goto fail; + } + } + + *dst_formats = formats; + return nr_formats; +fail: + audio_formats_free(formats, nrDefaultFormatsMax); + return 0; +} + +size_t server_rdpsnd_get_formats(AUDIO_FORMAT** dst_formats) +{ + /* Default supported audio formats */ + static const AUDIO_FORMAT default_supported_audio_formats[] = { + { WAVE_FORMAT_AAC_MS, 2, 44100, 176400, 4, 16, 0, NULL }, + { WAVE_FORMAT_MPEGLAYER3, 2, 44100, 176400, 4, 16, 0, NULL }, + { WAVE_FORMAT_MSG723, 2, 44100, 176400, 4, 16, 0, NULL }, + { WAVE_FORMAT_GSM610, 2, 44100, 176400, 4, 16, 0, NULL }, + { WAVE_FORMAT_ADPCM, 2, 44100, 176400, 4, 16, 0, NULL }, + { WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0, NULL }, + { WAVE_FORMAT_ALAW, 2, 22050, 44100, 2, 8, 0, NULL }, + { WAVE_FORMAT_MULAW, 2, 22050, 44100, 2, 8, 0, NULL }, + }; + AUDIO_FORMAT* supported_audio_formats = + audio_formats_new(ARRAYSIZE(default_supported_audio_formats)); + + if (!supported_audio_formats) + goto fail; + + size_t y = 0; + for (size_t x = 0; x < ARRAYSIZE(default_supported_audio_formats); x++) + { + const AUDIO_FORMAT* format = &default_supported_audio_formats[x]; + + if (freerdp_dsp_supports_format(format, TRUE)) + supported_audio_formats[y++] = *format; + } + + /* Set default audio formats. */ + *dst_formats = supported_audio_formats; + return y; +fail: + audio_formats_free(supported_audio_formats, ARRAYSIZE(default_supported_audio_formats)); + + if (dst_formats) + *dst_formats = NULL; + + return 0; +} + +void freerdp_server_warn_unmaintained(int argc, char* argv[]) +{ + const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV"; + const DWORD log_level = WLOG_WARN; + wLog* log = WLog_Get(TAG); + WINPR_ASSERT(log); + + if (!WLog_IsLevelActive(log, log_level)) + return; + + WLog_Print_unchecked(log, log_level, "[unmaintained] %s server is currently unmaintained!", + app); + WLog_Print_unchecked( + log, log_level, + " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for " + "known issues!"); + WLog_Print_unchecked( + log, log_level, + "Be prepared to fix issues yourself though as nobody is actively working on this."); + WLog_Print_unchecked( + log, log_level, + " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org " + "- dont hesitate to ask some questions. (replies might take some time depending " + "on your timezone) - if you intend using this component write us a message"); +} + +void freerdp_server_warn_experimental(int argc, char* argv[]) +{ + const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV"; + const DWORD log_level = WLOG_WARN; + wLog* log = WLog_Get(TAG); + WINPR_ASSERT(log); + + if (!WLog_IsLevelActive(log, log_level)) + return; + + WLog_Print_unchecked(log, log_level, "[experimental] %s server is currently experimental!", + app); + WLog_Print_unchecked( + log, log_level, + " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for " + "known issues or create a new one!"); + WLog_Print_unchecked( + log, log_level, + " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org " + "- dont hesitate to ask some questions. (replies might take some time depending " + "on your timezone)"); +} + +void freerdp_server_warn_deprecated(int argc, char* argv[]) +{ + const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV"; + const DWORD log_level = WLOG_WARN; + wLog* log = WLog_Get(TAG); + WINPR_ASSERT(log); + + if (!WLog_IsLevelActive(log, log_level)) + return; + + WLog_Print_unchecked(log, log_level, "[deprecated] %s server has been deprecated", app); + WLog_Print_unchecked(log, log_level, "As replacement there is a SDL based client available."); + WLog_Print_unchecked( + log, log_level, + "If you are interested in keeping %s alive get in touch with the developers", app); + WLog_Print_unchecked( + log, log_level, + "The project is hosted at https://github.com/freerdp/freerdp and " + " developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org " + "- dont hesitate to ask some questions. (replies might take some time depending " + "on your timezone)"); +} diff --git a/server/freerdp-server.pc.in b/server/freerdp-server.pc.in new file mode 100644 index 0000000..d7d6629 --- /dev/null +++ b/server/freerdp-server.pc.in @@ -0,0 +1,15 @@ +prefix=@PKG_CONFIG_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@FREERDP_INCLUDE_DIR@ +libs=-lfreerdp-server@FREERDP_API_VERSION@ + +Name: FreeRDP server +Description: FreeRDP: A Remote Desktop Protocol Implementation +URL: http://www.freerdp.com/ +Version: @FREERDP_VERSION@ +Requires: +Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_VERSION_MAJOR@ +Libs: -L${libdir} ${libs} +Libs.private: -ldl -lpthread +Cflags: -I${includedir} diff --git a/server/proxy/CMakeLists.txt b/server/proxy/CMakeLists.txt new file mode 100644 index 0000000..4693b17 --- /dev/null +++ b/server/proxy/CMakeLists.txt @@ -0,0 +1,131 @@ +# 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(CMakeDependentOption) +set(MODULE_NAME "freerdp-server-proxy") +set(MODULE_PREFIX "FREERDP_SERVER_PROXY") + +set(${MODULE_PREFIX}_SRCS + pf_context.c + pf_channel.c + pf_channel.h + pf_client.c + pf_client.h + pf_input.c + pf_input.h + pf_update.c + pf_update.h + pf_server.c + pf_server.h + pf_config.c + pf_modules.c + pf_utils.h + pf_utils.c + $<TARGET_OBJECTS:pf_channels> + ) + +set(PROXY_APP_SRCS freerdp_proxy.c) + +option(WITH_PROXY_EMULATE_SMARTCARD "Compile proxy smartcard emulation" OFF) +add_subdirectory("channels") + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + + configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( ${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) + list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION}) + +if (WITH_LIBRARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION}) +endif() + +set(PRIVATE_LIBS + freerdp-client + freerdp-server +) + +set(PUBLIC_LIBS + winpr + freerdp +) + +target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>) +target_link_libraries(${MODULE_NAME} PRIVATE ${PRIVATE_LIBS} PUBLIC ${PUBLIC_LIBS}) + +install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ProxyTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS) + get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Proxy") + +# pkg-config +include(pkg-config-install-prefix) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-proxy.pc.in ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR}) + +export(PACKAGE freerdp-proxy) + +SetFreeRDPCMakeInstallDir(FREERDP_PROXY_CMAKE_INSTALL_DIR "FreeRDP-Proxy${FREERDP_VERSION_MAJOR}") + +configure_package_config_file(FreeRDP-ProxyConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfig.cmake + INSTALL_DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR} + PATH_VARS FREERDP_INCLUDE_DIR) + +write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfigVersion.cmake + VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfigVersion.cmake + DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR}) +install(EXPORT FreeRDP-ProxyTargets DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/proxy") + +option(WITH_PROXY_APP "Compile proxy application" ON) + +if (WITH_PROXY_APP) + add_subdirectory("cli") +endif() + +option(WITH_PROXY_MODULES "Compile proxy modules" ON) +if (WITH_PROXY_MODULES) + add_subdirectory("modules") +endif() + diff --git a/server/proxy/FreeRDP-ProxyConfig.cmake.in b/server/proxy/FreeRDP-ProxyConfig.cmake.in new file mode 100644 index 0000000..406da3a --- /dev/null +++ b/server/proxy/FreeRDP-ProxyConfig.cmake.in @@ -0,0 +1,10 @@ + +@PACKAGE_INIT@ + +set(FreeRDP-Proxy_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@") +set(FreeRDP-Proxy_VERSION_MINOR "@FREERDP_VERSION_MINOR@") +set(FreeRDP-Proxy_VERSION_REVISION "@FREERDP_VERSION_REVISION@") + +set_and_check(FreeRDP-Proxy_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@") + +include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ProxyTargets.cmake") diff --git a/server/proxy/channels/CMakeLists.txt b/server/proxy/channels/CMakeLists.txt new file mode 100644 index 0000000..1915d83 --- /dev/null +++ b/server/proxy/channels/CMakeLists.txt @@ -0,0 +1,17 @@ + +set(MODULE_NAME pf_channels) +set(SOURCES + pf_channel_rdpdr.c + pf_channel_rdpdr.h + pf_channel_drdynvc.c + pf_channel_drdynvc.h +) + +if (WITH_PROXY_EMULATE_SMARTCARD) + list(APPEND SOURCES + pf_channel_smartcard.c + pf_channel_smartcard.h + ) +endif() + +add_library(${MODULE_NAME} OBJECT ${SOURCES}) diff --git a/server/proxy/channels/pf_channel_drdynvc.c b/server/proxy/channels/pf_channel_drdynvc.c new file mode 100644 index 0000000..9d8cab9 --- /dev/null +++ b/server/proxy/channels/pf_channel_drdynvc.c @@ -0,0 +1,711 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * pf_channel_drdynvc + * + * Copyright 2022 David Fort <contact@hardening-consulting.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/channels/drdynvc.h> +#include <freerdp/utils/drdynvc.h> +#include <freerdp/server/proxy/proxy_log.h> + +#include "pf_channel_drdynvc.h" +#include "../pf_channel.h" +#include "../proxy_modules.h" +#include "../pf_utils.h" + +#define DTAG PROXY_TAG("drdynvc") + +/** @brief channel opened status */ +typedef enum +{ + CHANNEL_OPENSTATE_WAITING_OPEN_STATUS, /*!< dynamic channel waiting for create response */ + CHANNEL_OPENSTATE_OPENED, /*!< opened */ + CHANNEL_OPENSTATE_CLOSED /*!< dynamic channel has been opened then closed */ +} PfDynChannelOpenStatus; + +typedef struct p_server_dynamic_channel_context pServerDynamicChannelContext; +typedef struct DynChannelTrackerState DynChannelTrackerState; + +typedef PfChannelResult (*dynamic_channel_on_data_fn)(pServerContext* ps, + pServerDynamicChannelContext* channel, + BOOL isBackData, ChannelStateTracker* tracker, + BOOL firstPacket, BOOL lastPacket); + +/** @brief tracker state for a drdynvc stream */ +struct DynChannelTrackerState +{ + UINT32 currentDataLength; + UINT32 CurrentDataReceived; + UINT32 CurrentDataFragments; + wStream* currentPacket; + dynamic_channel_on_data_fn dataCallback; +}; + +typedef void (*channel_data_dtor_fn)(void** user_data); + +struct p_server_dynamic_channel_context +{ + char* channelName; + UINT32 channelId; + PfDynChannelOpenStatus openStatus; + pf_utils_channel_mode channelMode; + BOOL packetReassembly; + DynChannelTrackerState backTracker; + DynChannelTrackerState frontTracker; + + void* channelData; + channel_data_dtor_fn channelDataDtor; +}; + +/** @brief context for the dynamic channel */ +typedef struct +{ + wHashTable* channels; + ChannelStateTracker* backTracker; + ChannelStateTracker* frontTracker; + wLog* log; +} DynChannelContext; + +/** @brief result of dynamic channel packet treatment */ +typedef enum +{ + DYNCVC_READ_OK, /*!< read was OK */ + DYNCVC_READ_ERROR, /*!< an error happened during read */ + DYNCVC_READ_INCOMPLETE /*!< missing bytes to read the complete packet */ +} DynvcReadResult; + +static const char* openstatus2str(PfDynChannelOpenStatus status) +{ + switch (status) + { + case CHANNEL_OPENSTATE_WAITING_OPEN_STATUS: + return "CHANNEL_OPENSTATE_WAITING_OPEN_STATUS"; + case CHANNEL_OPENSTATE_CLOSED: + return "CHANNEL_OPENSTATE_CLOSED"; + case CHANNEL_OPENSTATE_OPENED: + return "CHANNEL_OPENSTATE_OPENED"; + default: + return "CHANNEL_OPENSTATE_UNKNOWN"; + } +} + +static PfChannelResult data_cb(pServerContext* ps, pServerDynamicChannelContext* channel, + BOOL isBackData, ChannelStateTracker* tracker, BOOL firstPacket, + BOOL lastPacket) +{ + WINPR_ASSERT(ps); + WINPR_ASSERT(channel); + WINPR_ASSERT(tracker); + WINPR_ASSERT(ps->pdata); + + wStream* currentPacket = channelTracker_getCurrentPacket(tracker); + proxyDynChannelInterceptData dyn = { .name = channel->channelName, + .channelId = channel->channelId, + .data = currentPacket, + .isBackData = isBackData, + .first = firstPacket, + .last = lastPacket, + .rewritten = FALSE, + .packetSize = channelTracker_getCurrentPacketSize(tracker), + .result = PF_CHANNEL_RESULT_ERROR }; + Stream_SealLength(dyn.data); + if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_INTERCEPT_CHANNEL, ps->pdata, &dyn)) + return PF_CHANNEL_RESULT_ERROR; + + channelTracker_setCurrentPacketSize(tracker, dyn.packetSize); + if (dyn.rewritten) + return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData); + return dyn.result; +} + +static pServerDynamicChannelContext* DynamicChannelContext_new(wLog* log, pServerContext* ps, + const char* name, UINT32 id) +{ + WINPR_ASSERT(log); + + pServerDynamicChannelContext* ret = calloc(1, sizeof(*ret)); + if (!ret) + { + WLog_Print(log, WLOG_ERROR, "error allocating dynamic channel context '%s'", name); + return NULL; + } + + ret->channelId = id; + ret->channelName = _strdup(name); + if (!ret->channelName) + { + WLog_Print(log, WLOG_ERROR, "error allocating name in dynamic channel context '%s'", name); + free(ret); + return NULL; + } + + ret->frontTracker.dataCallback = data_cb; + ret->backTracker.dataCallback = data_cb; + + proxyChannelToInterceptData dyn = { .name = name, .channelId = id, .intercept = FALSE }; + if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_DYN_INTERCEPT_LIST, ps->pdata, &dyn) && + dyn.intercept) + ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT; + else + ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name); + ret->openStatus = CHANNEL_OPENSTATE_OPENED; + ret->packetReassembly = (ret->channelMode == PF_UTILS_CHANNEL_INTERCEPT); + + return ret; +} + +static void DynamicChannelContext_free(void* ptr) +{ + pServerDynamicChannelContext* c = (pServerDynamicChannelContext*)ptr; + if (!c) + return; + + if (c->backTracker.currentPacket) + Stream_Free(c->backTracker.currentPacket, TRUE); + + if (c->frontTracker.currentPacket) + Stream_Free(c->frontTracker.currentPacket, TRUE); + + if (c->channelDataDtor) + c->channelDataDtor(&c->channelData); + + free(c->channelName); + free(c); +} + +static UINT32 ChannelId_Hash(const void* key) +{ + const UINT32* v = (const UINT32*)key; + return *v; +} + +static BOOL ChannelId_Compare(const void* objA, const void* objB) +{ + const UINT32* v1 = objA; + const UINT32* v2 = objB; + return (*v1 == *v2); +} + +static DynvcReadResult dynvc_read_varInt(wLog* log, wStream* s, size_t len, UINT64* varInt, + BOOL last) +{ + WINPR_ASSERT(varInt); + switch (len) + { + case 0x00: + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 1)) + return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE; + Stream_Read_UINT8(s, *varInt); + break; + case 0x01: + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 2)) + return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE; + Stream_Read_UINT16(s, *varInt); + break; + case 0x02: + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4)) + return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE; + Stream_Read_UINT32(s, *varInt); + break; + case 0x03: + default: + WLog_Print(log, WLOG_ERROR, "Unknown int len %" PRIuz, len); + return DYNCVC_READ_ERROR; + } + return DYNCVC_READ_OK; +} + +static PfChannelResult DynvcTrackerPeekFn(ChannelStateTracker* tracker, BOOL firstPacket, + BOOL lastPacket) +{ + BYTE cmd = 0; + BYTE byte0 = 0; + wStream* s = NULL; + wStream sbuffer; + BOOL haveChannelId = 0; + BOOL haveLength = 0; + UINT64 dynChannelId = 0; + UINT64 Length = 0; + pServerDynamicChannelContext* dynChannel = NULL; + + WINPR_ASSERT(tracker); + + DynChannelContext* dynChannelContext = + (DynChannelContext*)channelTracker_getCustomData(tracker); + WINPR_ASSERT(dynChannelContext); + + BOOL isBackData = (tracker == dynChannelContext->backTracker); + DynChannelTrackerState* trackerState = NULL; + + UINT32 flags = lastPacket ? CHANNEL_FLAG_LAST : 0; + proxyData* pdata = channelTracker_getPData(tracker); + WINPR_ASSERT(pdata); + + const char* direction = isBackData ? "B->F" : "F->B"; + + { + wStream* currentPacket = channelTracker_getCurrentPacket(tracker); + s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(currentPacket), + Stream_GetPosition(currentPacket)); + } + + if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 1)) + return PF_CHANNEL_RESULT_ERROR; + + Stream_Read_UINT8(s, byte0); + cmd = byte0 >> 4; + + switch (cmd) + { + case CREATE_REQUEST_PDU: + case CLOSE_REQUEST_PDU: + case DATA_PDU: + case DATA_COMPRESSED_PDU: + haveChannelId = TRUE; + haveLength = FALSE; + break; + case DATA_FIRST_PDU: + case DATA_FIRST_COMPRESSED_PDU: + haveLength = TRUE; + haveChannelId = TRUE; + break; + default: + haveChannelId = FALSE; + haveLength = FALSE; + break; + } + + if (haveChannelId) + { + BYTE cbId = byte0 & 0x03; + + switch (dynvc_read_varInt(dynChannelContext->log, s, cbId, &dynChannelId, lastPacket)) + { + case DYNCVC_READ_OK: + break; + case DYNCVC_READ_INCOMPLETE: + return PF_CHANNEL_RESULT_DROP; + case DYNCVC_READ_ERROR: + default: + WLog_Print(dynChannelContext->log, WLOG_ERROR, + "DynvcTrackerPeekFn: invalid channelId field"); + return PF_CHANNEL_RESULT_ERROR; + } + + /* we always try to retrieve the dynamic channel in case it would have been opened + * and closed + */ + dynChannel = (pServerDynamicChannelContext*)HashTable_GetItemValue( + dynChannelContext->channels, &dynChannelId); + if (cmd != CREATE_REQUEST_PDU || !isBackData) + { + if (!dynChannel) + { + /* we've not found the target channel, so we drop this chunk, plus all the rest of + * the packet */ + channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP); + return PF_CHANNEL_RESULT_DROP; + } + } + } + + if (haveLength) + { + BYTE lenLen = (byte0 >> 2) & 0x03; + switch (dynvc_read_varInt(dynChannelContext->log, s, lenLen, &Length, lastPacket)) + { + case DYNCVC_READ_OK: + break; + case DYNCVC_READ_INCOMPLETE: + return PF_CHANNEL_RESULT_DROP; + case DYNCVC_READ_ERROR: + default: + WLog_Print(dynChannelContext->log, WLOG_ERROR, + "DynvcTrackerPeekFn: invalid length field"); + return PF_CHANNEL_RESULT_ERROR; + } + } + + switch (cmd) + { + case CAPABILITY_REQUEST_PDU: + WLog_Print(dynChannelContext->log, WLOG_DEBUG, "DynvcTracker: %s CAPABILITY_%s", + direction, isBackData ? "REQUEST" : "RESPONSE"); + channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS); + return PF_CHANNEL_RESULT_PASS; + + case CREATE_REQUEST_PDU: + { + UINT32 creationStatus = 0; + + /* we only want the full packet */ + if (!lastPacket) + return PF_CHANNEL_RESULT_DROP; + + if (isBackData) + { + proxyChannelDataEventInfo dev = { 0 }; + const char* name = Stream_ConstPointer(s); + const size_t nameLen = Stream_GetRemainingLength(s); + + const size_t len = strnlen(name, nameLen); + if ((len == 0) || (len == nameLen)) + return PF_CHANNEL_RESULT_ERROR; + + wStream* currentPacket = channelTracker_getCurrentPacket(tracker); + dev.channel_id = dynChannelId; + dev.channel_name = name; + dev.data = Stream_Buffer(s); + dev.data_len = Stream_GetPosition(currentPacket); + dev.flags = flags; + dev.total_size = Stream_GetPosition(currentPacket); + + if (dynChannel) + { + WLog_Print( + dynChannelContext->log, WLOG_WARN, + "Reusing channel id %" PRIu32 ", previously %s [state %s, mode %s], now %s", + dynChannel->channelId, dynChannel->channelName, + openstatus2str(dynChannel->openStatus), + pf_utils_channel_mode_string(dynChannel->channelMode), dev.channel_name); + + HashTable_Remove(dynChannelContext->channels, &dynChannel->channelId); + } + + if (!pf_modules_run_filter(pdata->module, + FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, pdata, + &dev)) + return PF_CHANNEL_RESULT_DROP; /* Silently drop */ + + dynChannel = DynamicChannelContext_new(dynChannelContext->log, pdata->ps, name, + dynChannelId); + if (!dynChannel) + { + WLog_Print(dynChannelContext->log, WLOG_ERROR, + "unable to create dynamic channel context data"); + return PF_CHANNEL_RESULT_ERROR; + } + + WLog_Print(dynChannelContext->log, WLOG_DEBUG, "Adding channel '%s'[%d]", + dynChannel->channelName, dynChannel->channelId); + if (!HashTable_Insert(dynChannelContext->channels, &dynChannel->channelId, + dynChannel)) + { + WLog_Print(dynChannelContext->log, WLOG_ERROR, + "unable register dynamic channel context data"); + DynamicChannelContext_free(dynChannel); + return PF_CHANNEL_RESULT_ERROR; + } + + dynChannel->openStatus = CHANNEL_OPENSTATE_WAITING_OPEN_STATUS; + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns dynChannel + return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, FALSE); + } + + /* CREATE_REQUEST_PDU response */ + if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 4)) + return PF_CHANNEL_RESULT_ERROR; + + Stream_Read_UINT32(s, creationStatus); + WLog_Print(dynChannelContext->log, WLOG_DEBUG, + "DynvcTracker(%" PRIu64 ",%s): %s CREATE_RESPONSE openStatus=%" PRIu32, + dynChannelId, dynChannel->channelName, direction, creationStatus); + + if (creationStatus == 0) + dynChannel->openStatus = CHANNEL_OPENSTATE_OPENED; + + return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, TRUE); + } + + case CLOSE_REQUEST_PDU: + if (!lastPacket) + return PF_CHANNEL_RESULT_DROP; + + WLog_Print(dynChannelContext->log, WLOG_DEBUG, + "DynvcTracker(%s): %s Close request on channel", dynChannel->channelName, + direction); + channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS); + if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED) + { + WLog_Print(dynChannelContext->log, WLOG_WARN, + "DynvcTracker(%s): is in state %s, expected %s", dynChannel->channelName, + openstatus2str(dynChannel->openStatus), + openstatus2str(CHANNEL_OPENSTATE_OPENED)); + } + dynChannel->openStatus = CHANNEL_OPENSTATE_CLOSED; + return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData); + + case SOFT_SYNC_REQUEST_PDU: + /* just pass then as is for now */ + WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_REQUEST_PDU"); + channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS); + /*TODO: return pf_treat_softsync_req(pdata, s);*/ + return PF_CHANNEL_RESULT_PASS; + + case SOFT_SYNC_RESPONSE_PDU: + /* just pass then as is for now */ + WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_RESPONSE_PDU"); + channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS); + return PF_CHANNEL_RESULT_PASS; + + case DATA_FIRST_PDU: + case DATA_PDU: + /* treat these below */ + trackerState = isBackData ? &dynChannel->backTracker : &dynChannel->frontTracker; + break; + + case DATA_FIRST_COMPRESSED_PDU: + case DATA_COMPRESSED_PDU: + WLog_Print(dynChannelContext->log, WLOG_DEBUG, + "TODO: compressed data packets, pass them as is for now"); + channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS); + return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData); + + default: + return PF_CHANNEL_RESULT_ERROR; + } + + if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED) + { + WLog_Print(dynChannelContext->log, WLOG_ERROR, + "DynvcTracker(%s [%s]): channel is not opened", dynChannel->channelName, + drdynvc_get_packet_type(cmd)); + return PF_CHANNEL_RESULT_ERROR; + } + + if ((cmd == DATA_FIRST_PDU) || (cmd == DATA_FIRST_COMPRESSED_PDU)) + { + WLog_Print(dynChannelContext->log, WLOG_DEBUG, + "DynvcTracker(%s [%s]): %s DATA_FIRST currentPacketLength=%" PRIu64 "", + dynChannel->channelName, drdynvc_get_packet_type(cmd), direction, Length); + trackerState->currentDataLength = Length; + trackerState->CurrentDataReceived = 0; + trackerState->CurrentDataFragments = 0; + + if (dynChannel->packetReassembly) + { + if (trackerState->currentPacket) + Stream_SetPosition(trackerState->currentPacket, 0); + } + } + + if (cmd == DATA_PDU || cmd == DATA_FIRST_PDU) + { + size_t extraSize = Stream_GetRemainingLength(s); + + trackerState->CurrentDataFragments++; + trackerState->CurrentDataReceived += extraSize; + + if (dynChannel->packetReassembly) + { + if (!trackerState->currentPacket) + { + trackerState->currentPacket = Stream_New(NULL, 1024); + if (!trackerState->currentPacket) + { + WLog_Print(dynChannelContext->log, WLOG_ERROR, + "unable to create current packet"); + return PF_CHANNEL_RESULT_ERROR; + } + } + + if (!Stream_EnsureRemainingCapacity(trackerState->currentPacket, extraSize)) + { + WLog_Print(dynChannelContext->log, WLOG_ERROR, "unable to grow current packet"); + return PF_CHANNEL_RESULT_ERROR; + } + + Stream_Write(trackerState->currentPacket, Stream_ConstPointer(s), extraSize); + } + WLog_Print(dynChannelContext->log, WLOG_DEBUG, + "DynvcTracker(%s [%s]): %s frags=%" PRIu32 " received=%" PRIu32 "(%" PRIu32 ")", + dynChannel->channelName, drdynvc_get_packet_type(cmd), direction, + trackerState->CurrentDataFragments, trackerState->CurrentDataReceived, + trackerState->currentDataLength); + } + + if (cmd == DATA_PDU) + { + if (trackerState->currentDataLength) + { + if (trackerState->CurrentDataReceived > trackerState->currentDataLength) + { + WLog_Print(dynChannelContext->log, WLOG_ERROR, + "DynvcTracker (%s [%s]): reassembled packet (%" PRIu32 + ") is bigger than announced length (%" PRIu32 ")", + dynChannel->channelName, drdynvc_get_packet_type(cmd), + trackerState->CurrentDataReceived, trackerState->currentDataLength); + return PF_CHANNEL_RESULT_ERROR; + } + } + else + { + trackerState->CurrentDataFragments = 0; + trackerState->CurrentDataReceived = 0; + } + } + + PfChannelResult result = PF_CHANNEL_RESULT_ERROR; + switch (dynChannel->channelMode) + { + case PF_UTILS_CHANNEL_PASSTHROUGH: + result = channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData); + break; + case PF_UTILS_CHANNEL_BLOCK: + channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP); + result = PF_CHANNEL_RESULT_DROP; + break; + case PF_UTILS_CHANNEL_INTERCEPT: + if (trackerState->dataCallback) + { + result = trackerState->dataCallback(pdata->ps, dynChannel, isBackData, tracker, + firstPacket, lastPacket); + } + else + { + WLog_Print(dynChannelContext->log, WLOG_ERROR, + "no intercept callback for channel %s(fromBack=%d), dropping packet", + dynChannel->channelName, isBackData); + result = PF_CHANNEL_RESULT_DROP; + } + break; + default: + WLog_Print(dynChannelContext->log, WLOG_ERROR, "unknown channel mode %d", + dynChannel->channelMode); + result = PF_CHANNEL_RESULT_ERROR; + break; + } + + if (!trackerState->currentDataLength || + (trackerState->CurrentDataReceived == trackerState->currentDataLength)) + { + trackerState->currentDataLength = 0; + trackerState->CurrentDataFragments = 0; + trackerState->CurrentDataReceived = 0; + + if (dynChannel->packetReassembly && trackerState->currentPacket) + Stream_SetPosition(trackerState->currentPacket, 0); + } + + return result; +} + +static void DynChannelContext_free(void* context) +{ + DynChannelContext* c = context; + if (!c) + return; + channelTracker_free(c->backTracker); + channelTracker_free(c->frontTracker); + HashTable_Free(c->channels); + free(c); +} + +static const char* dynamic_context(void* arg) +{ + proxyData* pdata = arg; + if (!pdata) + return "pdata=null"; + return pdata->session_id; +} + +static DynChannelContext* DynChannelContext_new(proxyData* pdata, + pServerStaticChannelContext* channel) +{ + DynChannelContext* dyn = calloc(1, sizeof(DynChannelContext)); + if (!dyn) + return FALSE; + + dyn->log = WLog_Get(DTAG); + WINPR_ASSERT(dyn->log); + WLog_SetContext(dyn->log, dynamic_context, pdata); + + dyn->backTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn); + if (!dyn->backTracker) + goto fail; + if (!channelTracker_setPData(dyn->backTracker, pdata)) + goto fail; + + dyn->frontTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn); + if (!dyn->frontTracker) + goto fail; + if (!channelTracker_setPData(dyn->frontTracker, pdata)) + goto fail; + + dyn->channels = HashTable_New(FALSE); + if (!dyn->channels) + goto fail; + + if (!HashTable_SetHashFunction(dyn->channels, ChannelId_Hash)) + goto fail; + + wObject* kobj = HashTable_KeyObject(dyn->channels); + WINPR_ASSERT(kobj); + kobj->fnObjectEquals = ChannelId_Compare; + + wObject* vobj = HashTable_ValueObject(dyn->channels); + WINPR_ASSERT(vobj); + vobj->fnObjectFree = DynamicChannelContext_free; + + return dyn; + +fail: + DynChannelContext_free(dyn); + return NULL; +} + +static PfChannelResult pf_dynvc_back_data(proxyData* pdata, + const pServerStaticChannelContext* channel, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + WINPR_ASSERT(channel); + + DynChannelContext* dyn = (DynChannelContext*)channel->context; + WINPR_UNUSED(pdata); + WINPR_ASSERT(dyn); + + return channelTracker_update(dyn->backTracker, xdata, xsize, flags, totalSize); +} + +static PfChannelResult pf_dynvc_front_data(proxyData* pdata, + const pServerStaticChannelContext* channel, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + WINPR_ASSERT(channel); + + DynChannelContext* dyn = (DynChannelContext*)channel->context; + WINPR_UNUSED(pdata); + WINPR_ASSERT(dyn); + + return channelTracker_update(dyn->frontTracker, xdata, xsize, flags, totalSize); +} + +BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel) +{ + DynChannelContext* ret = DynChannelContext_new(pdata, channel); + if (!ret) + return FALSE; + + channel->onBackData = pf_dynvc_back_data; + channel->onFrontData = pf_dynvc_front_data; + channel->contextDtor = DynChannelContext_free; + channel->context = ret; + return TRUE; +} diff --git a/server/proxy/channels/pf_channel_drdynvc.h b/server/proxy/channels/pf_channel_drdynvc.h new file mode 100644 index 0000000..b084143 --- /dev/null +++ b/server/proxy/channels/pf_channel_drdynvc.h @@ -0,0 +1,26 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * pf_channel_drdynvc + * + * Copyright 2022 David Fort <contact@hardening-consulting.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. + */ +#ifndef SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ +#define SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ + +#include <freerdp/server/proxy/proxy_context.h> + +BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel); + +#endif /* SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ */ diff --git a/server/proxy/channels/pf_channel_rdpdr.c b/server/proxy/channels/pf_channel_rdpdr.c new file mode 100644 index 0000000..cb79266 --- /dev/null +++ b/server/proxy/channels/pf_channel_rdpdr.c @@ -0,0 +1,2017 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak <armin.novak@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/assert.h> +#include <winpr/string.h> +#include <winpr/print.h> + +#include "pf_channel_rdpdr.h" +#include "pf_channel_smartcard.h" + +#include <freerdp/server/proxy/proxy_log.h> +#include <freerdp/channels/rdpdr.h> +#include <freerdp/channels/channels.h> +#include <freerdp/utils/rdpdr_utils.h> + +#define RTAG PROXY_TAG("channel.rdpdr") + +#define SCARD_DEVICE_ID UINT32_MAX + +typedef struct +{ + InterceptContextMapEntry base; + wStream* s; + wStream* buffer; + UINT16 versionMajor; + UINT16 versionMinor; + UINT32 clientID; + UINT32 computerNameLen; + BOOL computerNameUnicode; + union + { + WCHAR* wc; + char* c; + void* v; + } computerName; + UINT32 SpecialDeviceCount; + UINT32 capabilityVersions[6]; +} pf_channel_common_context; + +typedef enum +{ + STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST = 0x01, + STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST = 0x02, + STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM = 0x04, + STATE_CLIENT_CHANNEL_RUNNING = 0x10 +} pf_channel_client_state; + +typedef struct +{ + pf_channel_common_context common; + pf_channel_client_state state; + UINT32 flags; + UINT16 maxMajorVersion; + UINT16 maxMinorVersion; + wQueue* queue; + wLog* log; +} pf_channel_client_context; + +typedef enum +{ + STATE_SERVER_INITIAL, + STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY, + STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST, + STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE, + STATE_SERVER_CHANNEL_RUNNING +} pf_channel_server_state; + +typedef struct +{ + pf_channel_common_context common; + pf_channel_server_state state; + DWORD SessionId; + HANDLE handle; + wArrayList* blockedDevices; + wLog* log; +} pf_channel_server_context; + +#define proxy_client "[proxy<-->client]" +#define proxy_server "[proxy<-->server]" + +#define proxy_client_rx proxy_client " receive" +#define proxy_client_tx proxy_client " send" +#define proxy_server_rx proxy_server " receive" +#define proxy_server_tx proxy_server " send" + +#define SERVER_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_rx fmt, ##__VA_ARGS__) +#define CLIENT_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_rx fmt, ##__VA_ARGS__) +#define SERVER_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_tx fmt, ##__VA_ARGS__) +#define CLIENT_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_tx fmt, ##__VA_ARGS__) +#define RX_LOG(srv, lvl, fmt, ...) \ + do \ + { \ + if (srv) \ + { \ + SERVER_RX_LOG(lvl, fmt, ##__VA_ARGS__); \ + } \ + else \ + { \ + CLIENT_RX_LOG(lvl, fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define SERVER_RXTX_LOG(send, log, lvl, fmt, ...) \ + do \ + { \ + if (send) \ + { \ + SERVER_TX_LOG(log, lvl, fmt, ##__VA_ARGS__); \ + } \ + else \ + { \ + SERVER_RX_LOG(log, lvl, fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define Stream_CheckAndLogRequiredLengthSrv(log, s, len) \ + Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \ + proxy_client_rx " %s(%s:%" PRIuz ")", __func__, \ + __FILE__, (size_t)__LINE__) +#define Stream_CheckAndLogRequiredLengthClient(log, s, len) \ + Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \ + proxy_server_rx " %s(%s:%" PRIuz ")", __func__, \ + __FILE__, (size_t)__LINE__) +#define Stream_CheckAndLogRequiredLengthRx(srv, log, s, len) \ + Stream_CheckAndLogRequiredLengthRx_(srv, log, s, len, 1, __func__, __FILE__, __LINE__) +static BOOL Stream_CheckAndLogRequiredLengthRx_(BOOL srv, wLog* log, wStream* s, size_t nmemb, + size_t size, const char* fkt, const char* file, + size_t line) +{ + const char* fmt = + srv ? proxy_server_rx " %s(%s:%" PRIuz ")" : proxy_client_rx " %s(%s:%" PRIuz ")"; + + return Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, nmemb, size, fmt, fkt, file, + line); +} + +static const char* rdpdr_server_state_to_string(pf_channel_server_state state) +{ + switch (state) + { + case STATE_SERVER_INITIAL: + return "STATE_SERVER_INITIAL"; + case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY: + return "STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY"; + case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST: + return "STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST"; + case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE: + return "STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE"; + case STATE_SERVER_CHANNEL_RUNNING: + return "STATE_SERVER_CHANNEL_RUNNING"; + default: + return "STATE_SERVER_UNKNOWN"; + } +} + +static const char* rdpdr_client_state_to_string(pf_channel_client_state state) +{ + switch (state) + { + case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST: + return "STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST"; + case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST: + return "STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST"; + case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM: + return "STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM"; + case STATE_CLIENT_CHANNEL_RUNNING: + return "STATE_CLIENT_CHANNEL_RUNNING"; + default: + return "STATE_CLIENT_UNKNOWN"; + } +} + +static wStream* rdpdr_get_send_buffer(pf_channel_common_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->s); + if (!Stream_SetPosition(rdpdr->s, 0)) + return NULL; + if (!Stream_EnsureCapacity(rdpdr->s, capacity + 4)) + return NULL; + Stream_Write_UINT16(rdpdr->s, component); + Stream_Write_UINT16(rdpdr->s, PacketID); + return rdpdr->s; +} + +static wStream* rdpdr_client_get_send_buffer(pf_channel_client_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity); +} + +static wStream* rdpdr_server_get_send_buffer(pf_channel_server_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity); +} + +static UINT rdpdr_client_send(wLog* log, pClientContext* pc, wStream* s) +{ + UINT16 channelId = 0; + + WINPR_ASSERT(log); + WINPR_ASSERT(pc); + WINPR_ASSERT(s); + WINPR_ASSERT(pc->context.instance); + + if (!pc->connected) + { + CLIENT_TX_LOG(log, WLOG_WARN, "Ignoring channel %s message, not connected!", + RDPDR_SVC_CHANNEL_NAME); + return CHANNEL_RC_OK; + } + + channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME); + /* Ignore unmappable channels. Might happen when the channel was already down and + * some delayed message is tried to be sent. */ + if ((channelId == 0) || (channelId == UINT16_MAX)) + return ERROR_INTERNAL_ERROR; + + Stream_SealLength(s); + rdpdr_dump_send_packet(log, WLOG_TRACE, s, proxy_server_tx); + WINPR_ASSERT(pc->context.instance->SendChannelData); + if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, Stream_Buffer(s), + Stream_Length(s))) + return ERROR_EVT_CHANNEL_NOT_FOUND; + return CHANNEL_RC_OK; +} + +static UINT rdpdr_seal_send_free_request(pf_channel_server_context* context, wStream* s) +{ + BOOL status = 0; + size_t len = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->handle); + WINPR_ASSERT(s); + + Stream_SealLength(s); + len = Stream_Length(s); + WINPR_ASSERT(len <= ULONG_MAX); + + rdpdr_dump_send_packet(context->log, WLOG_TRACE, s, proxy_client_tx); + status = WTSVirtualChannelWrite(context->handle, (char*)Stream_Buffer(s), (ULONG)len, NULL); + return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static BOOL rdpdr_process_server_header(BOOL server, wLog* log, wStream* s, UINT16 component, + UINT16 PacketId, size_t expect) +{ + UINT16 rpacketid = 0; + UINT16 rcomponent = 0; + + WINPR_ASSERT(s); + if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, 4)) + { + RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: expected length 4, got %" PRIuz, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + Stream_GetRemainingLength(s)); + return FALSE; + } + + Stream_Read_UINT16(s, rcomponent); + Stream_Read_UINT16(s, rpacketid); + + if (rcomponent != component) + { + RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got component %s", + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + rdpdr_component_string(rcomponent)); + return FALSE; + } + + if (rpacketid != PacketId) + { + RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got PacketID %s", + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + rdpdr_packetid_string(rpacketid)); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, expect)) + { + RX_LOG(server, log, WLOG_WARN, + "RDPDR_HEADER[%s | %s] not enought data, expected %" PRIuz ", " + "got %" PRIuz, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), expect, + Stream_GetRemainingLength(s)); + return ERROR_INVALID_DATA; + } + + return TRUE; +} + +static BOOL rdpdr_check_version(BOOL server, wLog* log, UINT16 versionMajor, UINT16 versionMinor, + UINT16 component, UINT16 PacketId) +{ + if (versionMajor != RDPDR_VERSION_MAJOR) + { + RX_LOG(server, log, WLOG_WARN, "[%s | %s] expected MajorVersion %" PRIu16 ", got %" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + RDPDR_VERSION_MAJOR, versionMajor); + return FALSE; + } + switch (versionMinor) + { + case RDPDR_VERSION_MINOR_RDP50: + case RDPDR_VERSION_MINOR_RDP51: + case RDPDR_VERSION_MINOR_RDP52: + case RDPDR_VERSION_MINOR_RDP6X: + case RDPDR_VERSION_MINOR_RDP10X: + break; + default: + { + RX_LOG(server, log, WLOG_WARN, "[%s | %s] unsupported MinorVersion %" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + versionMinor); + return FALSE; + } + } + return TRUE; +} + +static UINT rdpdr_process_server_announce_request(pf_channel_client_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_SERVER_ANNOUNCE; + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, rdpdr->common.versionMajor); + Stream_Read_UINT16(s, rdpdr->common.versionMinor); + + if (!rdpdr_check_version(FALSE, rdpdr->log, rdpdr->common.versionMajor, + rdpdr->common.versionMinor, component, packetid)) + return ERROR_INVALID_DATA; + + /* Limit maximum channel protocol version to the one set by proxy server */ + if (rdpdr->common.versionMajor > rdpdr->maxMajorVersion) + { + rdpdr->common.versionMajor = rdpdr->maxMajorVersion; + rdpdr->common.versionMinor = rdpdr->maxMinorVersion; + } + else if (rdpdr->common.versionMinor > rdpdr->maxMinorVersion) + rdpdr->common.versionMinor = rdpdr->maxMinorVersion; + + Stream_Read_UINT32(s, rdpdr->common.clientID); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_send_announce_request(pf_channel_server_context* context) +{ + wStream* s = + rdpdr_server_get_send_buffer(context, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_ANNOUNCE, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, context->common.versionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, context->common.versionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->common.clientID); /* ClientId (4 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +static UINT rdpdr_process_client_announce_reply(pf_channel_server_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_CLIENTID_CONFIRM; + UINT16 versionMajor = 0; + UINT16 versionMinor = 0; + UINT32 clientID = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + + if (!rdpdr_check_version(TRUE, rdpdr->log, versionMajor, versionMinor, component, packetid)) + return ERROR_INVALID_DATA; + + if ((rdpdr->common.versionMajor != versionMajor) || + (rdpdr->common.versionMinor != versionMinor)) + { + SERVER_RX_LOG( + rdpdr->log, WLOG_WARN, + "[%s | %s] downgrading version from %" PRIu16 ".%" PRIu16 " to %" PRIu16 ".%" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor, versionMinor); + rdpdr->common.versionMajor = versionMajor; + rdpdr->common.versionMinor = versionMinor; + } + Stream_Read_UINT32(s, clientID); + if (rdpdr->common.clientID != clientID) + { + SERVER_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] changing clientID 0x%08" PRIu32 " to 0x%08" PRIu32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + rdpdr->common.clientID, clientID); + rdpdr->common.clientID = clientID; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_send_client_announce_reply(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + wStream* s = + rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, rdpdr->common.versionMajor); + Stream_Write_UINT16(s, rdpdr->common.versionMinor); + Stream_Write_UINT32(s, rdpdr->common.clientID); + return rdpdr_client_send(rdpdr->log, pc, s); +} + +static UINT rdpdr_process_client_name_request(pf_channel_server_context* rdpdr, wStream* s, + pClientContext* pc) +{ + UINT32 unicodeFlag = 0; + UINT32 codePage = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + WINPR_ASSERT(pc); + + if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME, + 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, unicodeFlag); + rdpdr->common.computerNameUnicode = (unicodeFlag & 1); + + Stream_Read_UINT32(s, codePage); + WINPR_UNUSED(codePage); /* Field is ignored */ + Stream_Read_UINT32(s, rdpdr->common.computerNameLen); + if (!Stream_CheckAndLogRequiredLengthSrv(rdpdr->log, s, rdpdr->common.computerNameLen)) + { + SERVER_RX_LOG( + rdpdr->log, WLOG_WARN, "[%s | %s]: missing data, got %" PRIu32 ", expected %" PRIu32, + rdpdr_component_string(RDPDR_CTYP_CORE), rdpdr_packetid_string(PAKID_CORE_CLIENT_NAME), + Stream_GetRemainingLength(s), rdpdr->common.computerNameLen); + return ERROR_INVALID_DATA; + } + void* tmp = realloc(rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + rdpdr->common.computerName.v = tmp; + + Stream_Read(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + + pc->computerNameLen = rdpdr->common.computerNameLen; + pc->computerNameUnicode = rdpdr->common.computerNameUnicode; + tmp = realloc(pc->computerName.v, pc->computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + pc->computerName.v = tmp; + memcpy(pc->computerName.v, rdpdr->common.computerName.v, pc->computerNameLen); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_send_client_name_request(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + wStream* s = NULL; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(pc); + + { + void* tmp = realloc(rdpdr->common.computerName.v, pc->computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + rdpdr->common.computerName.v = tmp; + rdpdr->common.computerNameLen = pc->computerNameLen; + rdpdr->common.computerNameUnicode = pc->computerNameUnicode; + memcpy(rdpdr->common.computerName.v, pc->computerName.v, pc->computerNameLen); + } + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME, + 12U + rdpdr->common.computerNameLen); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, rdpdr->common.computerNameUnicode + ? 1 + : 0); /* unicodeFlag, 0 for ASCII and 1 for Unicode */ + Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */ + Stream_Write_UINT32(s, rdpdr->common.computerNameLen); + Stream_Write(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + return rdpdr_client_send(rdpdr->log, pc, s); +} + +#define rdpdr_ignore_capset(srv, log, s, header) \ + rdpdr_ignore_capset_((srv), (log), (s), header, __func__) +static UINT rdpdr_ignore_capset_(BOOL srv, wLog* log, wStream* s, + const RDPDR_CAPABILITY_HEADER* header, const char* fkt) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_client_process_general_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_printer_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_port_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_drive_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_smartcard_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_server_core_capability_request(pf_channel_client_context* rdpdr, + wStream* s) +{ + UINT status = CHANNEL_RC_OK; + UINT16 numCapabilities = 0; + + WINPR_ASSERT(rdpdr); + + if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE, + PAKID_CORE_SERVER_CAPABILITY, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); + Stream_Seek(s, 2); /* pad (2 bytes) */ + + for (UINT16 i = 0; i < numCapabilities; i++) + { + RDPDR_CAPABILITY_HEADER header = { 0 }; + UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header); + if (error != CHANNEL_RC_OK) + return error; + + if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions)) + { + if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version) + rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version; + + WLog_Print(rdpdr->log, WLOG_TRACE, + "capability %s got version %" PRIu32 ", will use version %" PRIu32, + rdpdr_cap_type_string(header.CapabilityType), header.Version, + rdpdr->common.capabilityVersions[header.CapabilityType]); + } + + switch (header.CapabilityType) + { + case CAP_GENERAL_TYPE: + status = rdpdr_client_process_general_capset(rdpdr, s, &header); + break; + + case CAP_PRINTER_TYPE: + status = rdpdr_process_printer_capset(rdpdr, s, &header); + break; + + case CAP_PORT_TYPE: + status = rdpdr_process_port_capset(rdpdr, s, &header); + break; + + case CAP_DRIVE_TYPE: + status = rdpdr_process_drive_capset(rdpdr, s, &header); + break; + + case CAP_SMARTCARD_TYPE: + status = rdpdr_process_smartcard_capset(rdpdr, s, &header); + break; + + default: + WLog_Print(rdpdr->log, WLOG_WARN, + "unknown capability 0x%04" PRIx16 ", length %" PRIu16 + ", version %" PRIu32, + header.CapabilityType, header.CapabilityLength, header.Version); + Stream_Seek(s, header.CapabilityLength); + break; + } + + if (status != CHANNEL_RC_OK) + return status; + } + + return CHANNEL_RC_OK; +} + +static BOOL rdpdr_write_general_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, 44, + rdpdr->capabilityVersions[CAP_GENERAL_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + Stream_Write_UINT32(s, 0); /* osType, ignored on receipt */ + Stream_Write_UINT32(s, 0); /* osVersion, should be ignored */ + Stream_Write_UINT16(s, rdpdr->versionMajor); /* protocolMajorVersion, must be set to 1 */ + Stream_Write_UINT16(s, rdpdr->versionMinor); /* protocolMinorVersion */ + Stream_Write_UINT32(s, 0x0000FFFF); /* ioCode1 */ + Stream_Write_UINT32(s, 0); /* ioCode2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU | + RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */ + Stream_Write_UINT32(s, ENABLE_ASYNCIO); /* extraFlags1 */ + Stream_Write_UINT32(s, 0); /* extraFlags2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, rdpdr->SpecialDeviceCount); /* SpecialTypeDeviceCap, number of special + devices to be redirected before logon */ + return TRUE; +} + +static BOOL rdpdr_write_printer_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, 8, + rdpdr->capabilityVersions[CAP_PRINTER_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_port_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, 8, + rdpdr->capabilityVersions[CAP_PORT_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_drive_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, 8, + rdpdr->capabilityVersions[CAP_DRIVE_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_smartcard_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, 8, + rdpdr->capabilityVersions[CAP_SMARTCARD_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + return TRUE; +} + +static UINT rdpdr_send_server_capability_request(pf_channel_server_context* rdpdr) +{ + wStream* s = + rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_CAPABILITY, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + Stream_Write_UINT16(s, 5); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + return rdpdr_seal_send_free_request(rdpdr, s); +} + +static UINT rdpdr_process_client_capability_response(pf_channel_server_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_CLIENT_CAPABILITY; + UINT status = CHANNEL_RC_OK; + UINT16 numCapabilities = 0; + WINPR_ASSERT(rdpdr); + + if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); + Stream_Seek_UINT16(s); /* padding */ + + for (UINT16 x = 0; x < numCapabilities; x++) + { + RDPDR_CAPABILITY_HEADER header = { 0 }; + UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header); + if (error != CHANNEL_RC_OK) + return error; + if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions)) + { + if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version) + rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version; + + WLog_Print(rdpdr->log, WLOG_TRACE, + "capability %s got version %" PRIu32 ", will use version %" PRIu32, + rdpdr_cap_type_string(header.CapabilityType), header.Version, + rdpdr->common.capabilityVersions[header.CapabilityType]); + } + + switch (header.CapabilityType) + { + case CAP_GENERAL_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + case CAP_PRINTER_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + case CAP_PORT_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + case CAP_DRIVE_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + case CAP_SMARTCARD_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + default: + SERVER_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] invalid capability type 0x%04" PRIx16, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + header.CapabilityType); + status = ERROR_INVALID_DATA; + break; + } + + if (status != CHANNEL_RC_OK) + break; + } + + return status; +} + +static UINT rdpdr_send_client_capability_response(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s = NULL; + + WINPR_ASSERT(rdpdr); + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_CAPABILITY, 4); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, 5); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + return rdpdr_client_send(rdpdr->log, pc, s); +} + +static UINT rdpdr_send_server_clientid_confirm(pf_channel_server_context* rdpdr) +{ + wStream* s = NULL; + + s = rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + Stream_Write_UINT16(s, rdpdr->common.versionMajor); + Stream_Write_UINT16(s, rdpdr->common.versionMinor); + Stream_Write_UINT32(s, rdpdr->common.clientID); + return rdpdr_seal_send_free_request(rdpdr, s); +} + +static UINT rdpdr_process_server_clientid_confirm(pf_channel_client_context* rdpdr, wStream* s) +{ + UINT16 versionMajor = 0; + UINT16 versionMinor = 0; + UINT32 clientID = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE, + PAKID_CORE_CLIENTID_CONFIRM, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + if (!rdpdr_check_version(FALSE, rdpdr->log, versionMajor, versionMinor, RDPDR_CTYP_CORE, + PAKID_CORE_CLIENTID_CONFIRM)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, clientID); + + if ((versionMajor != rdpdr->common.versionMajor) || + (versionMinor != rdpdr->common.versionMinor)) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] Version mismatch, sent %" PRIu16 ".%" PRIu16 + ", downgraded to %" PRIu16 ".%" PRIu16, + rdpdr_component_string(RDPDR_CTYP_CORE), + rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), + rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor, + versionMinor); + rdpdr->common.versionMajor = versionMajor; + rdpdr->common.versionMinor = versionMinor; + } + + if (clientID != rdpdr->common.clientID) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] clientID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32, + rdpdr_component_string(RDPDR_CTYP_CORE), + rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), rdpdr->common.clientID, + clientID); + rdpdr->common.clientID = clientID; + } + + return CHANNEL_RC_OK; +} + +static BOOL +rdpdr_process_server_capability_request_or_clientid_confirm(pf_channel_client_context* rdpdr, + wStream* s) +{ + const UINT32 mask = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM | + STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + const UINT16 rcomponent = RDPDR_CTYP_CORE; + UINT16 component = 0; + UINT16 packetid = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if ((rdpdr->flags & mask) == mask) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "already past this state, abort!"); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLengthClient(rdpdr->log, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, component); + if (rcomponent != component) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got component %s, expected %s", + rdpdr_component_string(component), rdpdr_component_string(rcomponent)); + return FALSE; + } + Stream_Read_UINT16(s, packetid); + Stream_Rewind(s, 4); + + switch (packetid) + { + case PAKID_CORE_SERVER_CAPABILITY: + if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s", + rdpdr_packetid_string(packetid)); + return FALSE; + } + rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + return rdpdr_process_server_core_capability_request(rdpdr, s) == CHANNEL_RC_OK; + case PAKID_CORE_CLIENTID_CONFIRM: + default: + if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s", + rdpdr_packetid_string(packetid)); + return FALSE; + } + rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM; + return rdpdr_process_server_clientid_confirm(rdpdr, s) == CHANNEL_RC_OK; + } +} + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static UINT rdpdr_send_emulated_scard_device_list_announce_request(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s = NULL; + + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_ANNOUNCE, 24); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */ + Stream_Write_UINT32(s, RDPDR_DTYP_SMARTCARD); /* deviceType */ + Stream_Write_UINT32( + s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */ + Stream_Write(s, "SCARD\0\0\0", 8); + Stream_Write_UINT32(s, 6); + Stream_Write(s, "SCARD\0", 6); + + return rdpdr_client_send(rdpdr->log, pc, s); +} + +static UINT rdpdr_send_emulated_scard_device_remove(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s = NULL; + + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_REMOVE, 24); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */ + Stream_Write_UINT32( + s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */ + + return rdpdr_client_send(rdpdr->log, pc, s); +} + +static UINT rdpdr_process_server_device_announce_response(pf_channel_client_context* rdpdr, + wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_DEVICE_REPLY; + UINT32 deviceID = 0; + UINT32 resultCode = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, deviceID); + Stream_Read_UINT32(s, resultCode); + + if (deviceID != SCARD_DEVICE_ID) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] deviceID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + SCARD_DEVICE_ID, deviceID); + } + else if (resultCode != 0) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID, + resultCode); + } + else + CLIENT_RX_LOG(rdpdr->log, WLOG_DEBUG, + "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32 + " -> emulated smartcard redirected!", + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID, + resultCode); + + return CHANNEL_RC_OK; +} +#endif + +static BOOL pf_channel_rdpdr_rewrite_device_list_to(wStream* s, UINT32 fromVersion, + UINT32 toVersion) +{ + BOOL rc = FALSE; + if (fromVersion == toVersion) + return TRUE; + + const size_t cap = Stream_GetRemainingLength(s); + wStream* clone = Stream_New(NULL, cap); + if (!clone) + goto fail; + const size_t pos = Stream_GetPosition(s); + Stream_Copy(s, clone, cap); + Stream_SealLength(clone); + + Stream_SetPosition(clone, 0); + Stream_SetPosition(s, pos); + + /* Skip device count */ + if (!Stream_SafeSeek(s, 4)) + goto fail; + + UINT32 count = 0; + if (Stream_GetRemainingLength(clone) < 4) + goto fail; + Stream_Read_UINT32(clone, count); + + for (UINT32 x = 0; x < count; x++) + { + RdpdrDevice device = { 0 }; + const size_t charCount = ARRAYSIZE(device.PreferredDosName); + if (Stream_GetRemainingLength(clone) < 20) + goto fail; + + Stream_Read_UINT32(clone, device.DeviceType); /* DeviceType (4 bytes) */ + Stream_Read_UINT32(clone, device.DeviceId); /* DeviceId (4 bytes) */ + Stream_Read(clone, device.PreferredDosName, charCount); /* PreferredDosName (8 bytes) */ + Stream_Read_UINT32(clone, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */ + device.DeviceData = Stream_Pointer(clone); + if (!Stream_SafeSeek(clone, device.DeviceDataLength)) + goto fail; + + if (!Stream_EnsureRemainingCapacity(s, 20)) + goto fail; + Stream_Write_UINT32(s, device.DeviceType); + Stream_Write_UINT32(s, device.DeviceId); + Stream_Write(s, device.PreferredDosName, charCount); + + if (device.DeviceType == RDPDR_DTYP_FILESYSTEM) + { + if (toVersion == DRIVE_CAPABILITY_VERSION_01) + Stream_Write_UINT32(s, 0); /* No unicode name */ + else + { + const size_t datalen = charCount * sizeof(WCHAR); + if (!Stream_EnsureRemainingCapacity(s, datalen + sizeof(UINT32))) + goto fail; + Stream_Write_UINT32(s, datalen); + + const SSIZE_T rcw = Stream_Write_UTF16_String_From_UTF8( + s, charCount, device.PreferredDosName, charCount - 1, TRUE); + if (rcw < 0) + goto fail; + } + } + else + { + Stream_Write_UINT32(s, device.DeviceDataLength); + if (!Stream_EnsureRemainingCapacity(s, device.DeviceDataLength)) + goto fail; + Stream_Write(s, device.DeviceData, device.DeviceDataLength); + } + } + + Stream_SealLength(s); + rc = TRUE; + +fail: + Stream_Free(clone, TRUE); + return rc; +} + +static BOOL pf_channel_rdpdr_rewrite_device_list(pf_channel_client_context* rdpdr, + pServerContext* ps, wStream* s, BOOL toServer) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(ps); + + const size_t pos = Stream_GetPosition(s); + UINT16 component = 0; + UINT16 packetid = 0; + Stream_SetPosition(s, 0); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, component); + Stream_Read_UINT16(s, packetid); + if ((component != RDPDR_CTYP_CORE) || (packetid != PAKID_CORE_DEVICELIST_ANNOUNCE)) + { + Stream_SetPosition(s, pos); + return TRUE; + } + + const pf_channel_server_context* srv = + HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); + if (!srv) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "No channel %s in intercep map", RDPDR_SVC_CHANNEL_NAME); + return FALSE; + } + + UINT32 from = srv->common.capabilityVersions[CAP_DRIVE_TYPE]; + UINT32 to = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE]; + if (toServer) + { + from = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE]; + to = srv->common.capabilityVersions[CAP_DRIVE_TYPE]; + } + if (!pf_channel_rdpdr_rewrite_device_list_to(s, from, to)) + return FALSE; + + Stream_SetPosition(s, pos); + return TRUE; +} + +static BOOL pf_channel_rdpdr_client_send_to_server(pf_channel_client_context* rdpdr, + pServerContext* ps, wStream* s) +{ + WINPR_ASSERT(rdpdr); + if (ps) + { + UINT16 server_channel_id = WTSChannelGetId(ps->context.peer, RDPDR_SVC_CHANNEL_NAME); + + /* Ignore messages for channels that can not be mapped. + * The client might not have enabled support for this specific channel, + * so just drop the message. */ + if (server_channel_id == 0) + return TRUE; + + if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, TRUE)) + return FALSE; + size_t len = Stream_Length(s); + Stream_SetPosition(s, len); + rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_tx); + WINPR_ASSERT(ps->context.peer); + WINPR_ASSERT(ps->context.peer->SendChannelData); + return ps->context.peer->SendChannelData(ps->context.peer, server_channel_id, + Stream_Buffer(s), len); + } + return TRUE; +} + +static BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr); + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static BOOL rdpdr_process_server_loggedon_request(pServerContext* ps, pClientContext* pc, + pf_channel_client_context* rdpdr, wStream* s, + UINT16 component, UINT16 packetid) +{ + WINPR_ASSERT(rdpdr); + WLog_Print(rdpdr->log, WLOG_DEBUG, "[%s | %s]", rdpdr_component_string(component), + rdpdr_packetid_string(packetid)); + if (rdpdr_send_emulated_scard_device_remove(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s); +} + +static BOOL filter_smartcard_io_requests(pf_channel_client_context* rdpdr, wStream* s, + UINT16* pPacketid) +{ + BOOL rc = FALSE; + UINT16 component = 0; + UINT16 packetid = 0; + UINT32 deviceID = 0; + size_t pos = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(pPacketid); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4)) + return FALSE; + + pos = Stream_GetPosition(s); + Stream_Read_UINT16(s, component); + Stream_Read_UINT16(s, packetid); + + if (Stream_GetRemainingLength(s) >= 4) + Stream_Read_UINT32(s, deviceID); + + WLog_Print(rdpdr->log, WLOG_DEBUG, "got: [%s | %s]: [0x%08" PRIx32 "]", + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID); + + if (component != RDPDR_CTYP_CORE) + goto fail; + + switch (packetid) + { + case PAKID_CORE_SERVER_ANNOUNCE: + case PAKID_CORE_CLIENTID_CONFIRM: + case PAKID_CORE_CLIENT_NAME: + case PAKID_CORE_DEVICELIST_ANNOUNCE: + case PAKID_CORE_DEVICELIST_REMOVE: + case PAKID_CORE_SERVER_CAPABILITY: + case PAKID_CORE_CLIENT_CAPABILITY: + WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]", + rdpdr_component_string(component), rdpdr_packetid_string(packetid)); + *pPacketid = packetid; + break; + case PAKID_CORE_USER_LOGGEDON: + *pPacketid = packetid; + break; + case PAKID_CORE_DEVICE_REPLY: + case PAKID_CORE_DEVICE_IOREQUEST: + if (deviceID != SCARD_DEVICE_ID) + goto fail; + *pPacketid = packetid; + break; + default: + if (deviceID != SCARD_DEVICE_ID) + goto fail; + WLog_Print(rdpdr->log, WLOG_WARN, + "Got [%s | %s] for deviceID 0x%08" PRIx32 ", TODO: Not handled!", + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + deviceID); + goto fail; + } + + rc = TRUE; + +fail: + Stream_SetPosition(s, pos); + return rc; +} +#endif + +BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + UINT16 channelId = 0; + + WINPR_ASSERT(pc); + WINPR_ASSERT(rdpdr); + + if (rdpdr->state != STATE_CLIENT_CHANNEL_RUNNING) + return FALSE; + channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME); + if ((channelId == 0) || (channelId == UINT16_MAX)) + return TRUE; + + Queue_Lock(rdpdr->queue); + while (Queue_Count(rdpdr->queue) > 0) + { + wStream* s = Queue_Dequeue(rdpdr->queue); + if (!s) + continue; + + size_t len = Stream_Length(s); + Stream_SetPosition(s, len); + + rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_tx " (queue) "); + WINPR_ASSERT(pc->context.instance->SendChannelData); + if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, + Stream_Buffer(s), len)) + { + CLIENT_TX_LOG(rdpdr->log, WLOG_ERROR, "xxxxxx TODO: Failed to send data!"); + } + Stream_Free(s, TRUE); + } + Queue_Unlock(rdpdr->queue); + return TRUE; +} + +static BOOL rdpdr_handle_server_announce_request(pClientContext* pc, + pf_channel_client_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (rdpdr_process_server_announce_request(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_client_announce_reply(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_client_name_request(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + return TRUE; +} + +BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize) +{ + pf_channel_client_context* rdpdr = NULL; + pServerContext* ps = NULL; + wStream* s = NULL; +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + UINT16 packetid = 0; +#endif + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + WINPR_ASSERT(pc->interceptContextMap); + WINPR_ASSERT(channel_name); + WINPR_ASSERT(xdata); + + ps = pc->pdata->ps; + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name); + if (!rdpdr) + { + CLIENT_RX_LOG(WLog_Get(RTAG), WLOG_ERROR, + "Channel %s [0x%04" PRIx16 "] missing context in interceptContextMap", + channel_name, channelId); + return FALSE; + } + s = rdpdr->common.buffer; + if (flags & CHANNEL_FLAG_FIRST) + Stream_SetPosition(s, 0); + if (!Stream_EnsureRemainingCapacity(s, xsize)) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR, + "Channel %s [0x%04" PRIx16 "] not enough memory [need %" PRIuz "]", + channel_name, channelId, xsize); + return FALSE; + } + Stream_Write(s, xdata, xsize); + if ((flags & CHANNEL_FLAG_LAST) == 0) + return TRUE; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + if (Stream_Length(s) != totalSize) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "Received invalid %s channel data (server -> proxy), expected %" PRIuz + "bytes, got %" PRIuz, + channel_name, totalSize, Stream_Length(s)); + return FALSE; + } + + rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_rx); + switch (rdpdr->state) + { + case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST: + if (!rdpdr_handle_server_announce_request(pc, rdpdr, s)) + return FALSE; + break; + case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST: + if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s)) + return FALSE; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM; + break; + case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM: + if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s)) + return FALSE; + if (rdpdr_send_client_capability_response(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (pf_channel_smartcard_client_emulate(pc)) + { + if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != + CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING; + } + else +#endif + { + rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING; + pf_channel_send_client_queue(pc, rdpdr); + } + + break; + case STATE_CLIENT_CHANNEL_RUNNING: +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (!pf_channel_smartcard_client_emulate(pc) || + !filter_smartcard_io_requests(rdpdr, s, &packetid)) + return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s); + else + { + switch (packetid) + { + case PAKID_CORE_USER_LOGGEDON: + return rdpdr_process_server_loggedon_request(ps, pc, rdpdr, s, + RDPDR_CTYP_CORE, packetid); + case PAKID_CORE_DEVICE_IOREQUEST: + { + wStream* out = rdpdr_client_get_send_buffer( + rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOCOMPLETION, 0); + WINPR_ASSERT(out); + + if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE, + PAKID_CORE_DEVICE_IOREQUEST, 20)) + return FALSE; + + if (!pf_channel_smartcard_client_handle(rdpdr->log, pc, s, out, + rdpdr_client_send)) + return FALSE; + } + break; + case PAKID_CORE_SERVER_ANNOUNCE: + pf_channel_rdpdr_client_reset(pc); + if (!rdpdr_handle_server_announce_request(pc, rdpdr, s)) + return FALSE; + break; + case PAKID_CORE_SERVER_CAPABILITY: + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + rdpdr->flags = 0; + return pf_channel_rdpdr_client_handle(pc, channelId, channel_name, xdata, + xsize, flags, totalSize); + case PAKID_CORE_DEVICE_REPLY: + break; + default: + CLIENT_RX_LOG( + rdpdr->log, WLOG_ERROR, + "Channel %s [0x%04" PRIx16 + "] we´ve reached an impossible state %s! [%s] aliens invaded!", + channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state), + rdpdr_packetid_string(packetid)); + return FALSE; + } + } + break; +#else + return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s); +#endif + default: + CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR, + "Channel %s [0x%04" PRIx16 + "] we´ve reached an impossible state %s! aliens invaded!", + channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state)); + return FALSE; + } + + return TRUE; +} + +static void pf_channel_rdpdr_common_context_free(pf_channel_common_context* common) +{ + if (!common) + return; + free(common->computerName.v); + Stream_Free(common->s, TRUE); + Stream_Free(common->buffer, TRUE); +} + +static void pf_channel_rdpdr_client_context_free(InterceptContextMapEntry* base) +{ + pf_channel_client_context* entry = (pf_channel_client_context*)base; + if (!entry) + return; + + pf_channel_rdpdr_common_context_free(&entry->common); + Queue_Free(entry->queue); + free(entry); +} + +static BOOL pf_channel_rdpdr_common_context_new(pf_channel_common_context* common, + void (*fkt)(InterceptContextMapEntry*)) +{ + if (!common) + return FALSE; + common->base.free = fkt; + common->s = Stream_New(NULL, 1024); + if (!common->s) + return FALSE; + common->buffer = Stream_New(NULL, 1024); + if (!common->buffer) + return FALSE; + common->computerNameUnicode = 1; + common->computerName.v = NULL; + common->versionMajor = RDPDR_VERSION_MAJOR; + common->versionMinor = RDPDR_VERSION_MINOR_RDP10X; + common->clientID = SCARD_DEVICE_ID; + + const UINT32 versions[] = { 0, + GENERAL_CAPABILITY_VERSION_02, + PRINT_CAPABILITY_VERSION_01, + PORT_CAPABILITY_VERSION_01, + DRIVE_CAPABILITY_VERSION_02, + SMARTCARD_CAPABILITY_VERSION_01 }; + + memcpy(common->capabilityVersions, versions, sizeof(common->capabilityVersions)); + return TRUE; +} + +static BOOL pf_channel_rdpdr_client_pass_message(pServerContext* ps, pClientContext* pc, + UINT16 channelId, const char* channel_name, + wStream* s) +{ + pf_channel_client_context* rdpdr = NULL; + + WINPR_ASSERT(ps); + WINPR_ASSERT(pc); + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name); + if (!rdpdr) + return TRUE; /* Ignore data for channels not available on proxy -> server connection */ + WINPR_ASSERT(rdpdr->queue); + + if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, FALSE)) + return FALSE; + if (!Queue_Enqueue(rdpdr->queue, s)) + return FALSE; + pf_channel_send_client_queue(pc, rdpdr); + return TRUE; +} + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static BOOL filter_smartcard_device_list_remove(pf_channel_server_context* rdpdr, wStream* s) +{ + size_t pos = 0; + UINT32 count = 0; + + WINPR_ASSERT(rdpdr); + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32))) + return TRUE; + pos = Stream_GetPosition(s); + Stream_Read_UINT32(s, count); + + if (count == 0) + return TRUE; + + if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(rdpdr->log, s, count, sizeof(UINT32))) + return TRUE; + + for (UINT32 x = 0; x < count; x++) + { + UINT32 deviceID = 0; + BYTE* dst = Stream_Pointer(s); + Stream_Read_UINT32(s, deviceID); + if (deviceID == SCARD_DEVICE_ID) + { + ArrayList_Remove(rdpdr->blockedDevices, (void*)(size_t)deviceID); + + /* This is the only device, filter it! */ + if (count == 1) + return TRUE; + + /* Remove this device from the list */ + memmove(dst, Stream_ConstPointer(s), (count - x - 1) * sizeof(UINT32)); + + count--; + Stream_SetPosition(s, pos); + Stream_Write_UINT32(s, count); + return FALSE; + } + } + + return FALSE; +} + +static BOOL filter_smartcard_device_io_request(pf_channel_server_context* rdpdr, wStream* s) +{ + UINT32 DeviceID = 0; + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + Stream_Read_UINT32(s, DeviceID); + return ArrayList_Contains(rdpdr->blockedDevices, (void*)(size_t)DeviceID); +} + +static BOOL filter_smartcard_device_list_announce(pf_channel_server_context* rdpdr, wStream* s) +{ + UINT32 count = 0; + + WINPR_ASSERT(rdpdr); + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32))) + return TRUE; + const size_t pos = Stream_GetPosition(s); + Stream_Read_UINT32(s, count); + + if (count == 0) + return TRUE; + + for (UINT32 x = 0; x < count; x++) + { + UINT32 DeviceType = 0; + UINT32 DeviceId = 0; + char PreferredDosName[8]; + UINT32 DeviceDataLength = 0; + BYTE* dst = Stream_Pointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 20)) + return TRUE; + Stream_Read_UINT32(s, DeviceType); + Stream_Read_UINT32(s, DeviceId); + Stream_Read(s, PreferredDosName, ARRAYSIZE(PreferredDosName)); + Stream_Read_UINT32(s, DeviceDataLength); + if (!Stream_SafeSeek(s, DeviceDataLength)) + return TRUE; + if (DeviceType == RDPDR_DTYP_SMARTCARD) + { + ArrayList_Append(rdpdr->blockedDevices, (void*)(size_t)DeviceId); + if (count == 1) + return TRUE; + + WLog_Print(rdpdr->log, WLOG_INFO, "Filtering smartcard device 0x%08" PRIx32 "", + DeviceId); + + memmove(dst, Stream_ConstPointer(s), Stream_GetRemainingLength(s)); + Stream_SetPosition(s, pos); + Stream_Write_UINT32(s, count - 1); + return FALSE; + } + } + + return FALSE; +} + +static BOOL filter_smartcard_device_list_announce_request(pf_channel_server_context* rdpdr, + wStream* s) +{ + BOOL rc = TRUE; + size_t pos = 0; + UINT16 component = 0; + UINT16 packetid = 0; + + WINPR_ASSERT(rdpdr); + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8)) + return FALSE; + + pos = Stream_GetPosition(s); + + Stream_Read_UINT16(s, component); + Stream_Read_UINT16(s, packetid); + + if (component != RDPDR_CTYP_CORE) + goto fail; + + switch (packetid) + { + case PAKID_CORE_DEVICELIST_ANNOUNCE: + if (filter_smartcard_device_list_announce(rdpdr, s)) + goto fail; + break; + case PAKID_CORE_DEVICELIST_REMOVE: + if (filter_smartcard_device_list_remove(rdpdr, s)) + goto fail; + break; + case PAKID_CORE_DEVICE_IOREQUEST: + if (filter_smartcard_device_io_request(rdpdr, s)) + goto fail; + break; + + case PAKID_CORE_SERVER_ANNOUNCE: + case PAKID_CORE_CLIENTID_CONFIRM: + case PAKID_CORE_CLIENT_NAME: + case PAKID_CORE_DEVICE_REPLY: + case PAKID_CORE_SERVER_CAPABILITY: + case PAKID_CORE_CLIENT_CAPABILITY: + case PAKID_CORE_USER_LOGGEDON: + WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]", + rdpdr_component_string(component), rdpdr_packetid_string(packetid)); + goto fail; + default: + break; + } + + rc = FALSE; +fail: + Stream_SetPosition(s, pos); + return rc; +} +#endif + +static void* stream_copy(const void* obj) +{ + const wStream* src = obj; + wStream* dst = Stream_New(NULL, Stream_Capacity(src)); + if (!dst) + return NULL; + memcpy(Stream_Buffer(dst), Stream_ConstBuffer(src), Stream_Capacity(dst)); + Stream_SetLength(dst, Stream_Length(src)); + Stream_SetPosition(dst, Stream_GetPosition(src)); + return dst; +} + +static void stream_free(void* obj) +{ + wStream* s = obj; + Stream_Free(s, TRUE); +} + +static const char* pf_channel_rdpdr_client_context(void* arg) +{ + pClientContext* pc = arg; + if (!pc) + return "pc=null"; + if (!pc->pdata) + return "pc->pdata=null"; + return pc->pdata->session_id; +} + +BOOL pf_channel_rdpdr_client_new(pClientContext* pc) +{ + wObject* obj = NULL; + pf_channel_client_context* rdpdr = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + rdpdr = calloc(1, sizeof(pf_channel_client_context)); + if (!rdpdr) + return FALSE; + rdpdr->log = WLog_Get(RTAG); + WINPR_ASSERT(rdpdr->log); + + WLog_SetContext(rdpdr->log, pf_channel_rdpdr_client_context, pc); + if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_client_context_free)) + goto fail; + + rdpdr->maxMajorVersion = RDPDR_VERSION_MAJOR; + rdpdr->maxMinorVersion = RDPDR_VERSION_MINOR_RDP10X; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST; + + rdpdr->queue = Queue_New(TRUE, 0, 0); + if (!rdpdr->queue) + goto fail; + obj = Queue_Object(rdpdr->queue); + WINPR_ASSERT(obj); + obj->fnObjectNew = stream_copy; + obj->fnObjectFree = stream_free; + if (!HashTable_Insert(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr)) + goto fail; + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr + return TRUE; +fail: + pf_channel_rdpdr_client_context_free(&rdpdr->common.base); + return FALSE; +} + +void pf_channel_rdpdr_client_free(pClientContext* pc) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + HashTable_Remove(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); +} + +static void pf_channel_rdpdr_server_context_free(InterceptContextMapEntry* base) +{ + pf_channel_server_context* entry = (pf_channel_server_context*)base; + if (!entry) + return; + + WTSVirtualChannelClose(entry->handle); + pf_channel_rdpdr_common_context_free(&entry->common); + ArrayList_Free(entry->blockedDevices); + free(entry); +} + +static const char* pf_channel_rdpdr_server_context(void* arg) +{ + pServerContext* ps = arg; + if (!ps) + return "ps=null"; + if (!ps->pdata) + return "ps->pdata=null"; + return ps->pdata->session_id; +} + +BOOL pf_channel_rdpdr_server_new(pServerContext* ps) +{ + pf_channel_server_context* rdpdr = NULL; + PULONG pSessionId = NULL; + DWORD BytesReturned = 0; + + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + + rdpdr = calloc(1, sizeof(pf_channel_server_context)); + if (!rdpdr) + return FALSE; + rdpdr->log = WLog_Get(RTAG); + WINPR_ASSERT(rdpdr->log); + WLog_SetContext(rdpdr->log, pf_channel_rdpdr_server_context, ps); + + if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_server_context_free)) + goto fail; + rdpdr->state = STATE_SERVER_INITIAL; + + rdpdr->blockedDevices = ArrayList_New(FALSE); + if (!rdpdr->blockedDevices) + goto fail; + + rdpdr->SessionId = WTS_CURRENT_SESSION; + if (WTSQuerySessionInformationA(ps->vcm, WTS_CURRENT_SESSION, WTSSessionId, (LPSTR*)&pSessionId, + &BytesReturned)) + { + rdpdr->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + } + + rdpdr->handle = WTSVirtualChannelOpenEx(rdpdr->SessionId, RDPDR_SVC_CHANNEL_NAME, 0); + if (rdpdr->handle == 0) + goto fail; + if (!HashTable_Insert(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr)) + goto fail; + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr + return TRUE; +fail: + pf_channel_rdpdr_server_context_free(&rdpdr->common.base); + return FALSE; +} + +void pf_channel_rdpdr_server_free(pServerContext* ps) +{ + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + HashTable_Remove(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); +} + +static pf_channel_server_context* get_channel(pServerContext* ps, BOOL send) +{ + pf_channel_server_context* rdpdr = NULL; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + + rdpdr = HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); + if (!rdpdr) + { + SERVER_RXTX_LOG(send, WLog_Get(RTAG), WLOG_ERROR, + "Channel %s missing context in interceptContextMap", + RDPDR_SVC_CHANNEL_NAME); + return NULL; + } + + return rdpdr; +} + +BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize) +{ + wStream* s = NULL; + pClientContext* pc = NULL; + pf_channel_server_context* rdpdr = get_channel(ps, FALSE); + if (!rdpdr) + return FALSE; + + WINPR_ASSERT(ps->pdata); + pc = ps->pdata->pc; + + s = rdpdr->common.buffer; + + if (flags & CHANNEL_FLAG_FIRST) + Stream_SetPosition(s, 0); + + if (!Stream_EnsureRemainingCapacity(s, xsize)) + return FALSE; + Stream_Write(s, xdata, xsize); + + if ((flags & CHANNEL_FLAG_LAST) == 0) + return TRUE; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + + if (Stream_Length(s) != totalSize) + { + SERVER_RX_LOG(rdpdr->log, WLOG_WARN, + "Received invalid %s channel data (client -> proxy), expected %" PRIuz + "bytes, got %" PRIuz, + channel_name, totalSize, Stream_Length(s)); + return FALSE; + } + + rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_rx); + switch (rdpdr->state) + { + case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY: + if (rdpdr_process_client_announce_reply(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST; + break; + case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST: + if (rdpdr_process_client_name_request(rdpdr, s, pc) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_server_capability_request(rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_server_clientid_confirm(rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE; + break; + case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE: + if (rdpdr_process_client_capability_response(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_CHANNEL_RUNNING; + break; + case STATE_SERVER_CHANNEL_RUNNING: +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (!pf_channel_smartcard_client_emulate(pc) || + !filter_smartcard_device_list_announce_request(rdpdr, s)) + { + if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s)) + return FALSE; + } + else + return pf_channel_smartcard_server_handle(ps, s); +#else + if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s)) + return FALSE; +#endif + break; + default: + case STATE_SERVER_INITIAL: + SERVER_RX_LOG(rdpdr->log, WLOG_WARN, "Invalid state %s", + rdpdr_server_state_to_string(rdpdr->state)); + return FALSE; + } + + return TRUE; +} + +BOOL pf_channel_rdpdr_server_announce(pServerContext* ps) +{ + pf_channel_server_context* rdpdr = get_channel(ps, TRUE); + if (!rdpdr) + return FALSE; + + WINPR_ASSERT(rdpdr->state == STATE_SERVER_INITIAL); + if (rdpdr_server_send_announce_request(rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY; + return TRUE; +} + +BOOL pf_channel_rdpdr_client_reset(pClientContext* pc) +{ + pf_channel_client_context* rdpdr = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + WINPR_ASSERT(pc->interceptContextMap); + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); + if (!rdpdr) + return TRUE; + + Queue_Clear(rdpdr->queue); + rdpdr->flags = 0; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST; + + return TRUE; +} + +static PfChannelResult pf_rdpdr_back_data(proxyData* pdata, + const pServerStaticChannelContext* channel, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + if (!pf_channel_rdpdr_client_handle(pdata->pc, channel->back_channel_id, channel->channel_name, + xdata, xsize, flags, totalSize)) + return PF_CHANNEL_RESULT_ERROR; + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (pf_channel_smartcard_client_emulate(pdata->pc)) + return PF_CHANNEL_RESULT_DROP; +#endif + return PF_CHANNEL_RESULT_DROP; +} + +static PfChannelResult pf_rdpdr_front_data(proxyData* pdata, + const pServerStaticChannelContext* channel, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + if (!pf_channel_rdpdr_server_handle(pdata->ps, channel->front_channel_id, channel->channel_name, + xdata, xsize, flags, totalSize)) + return PF_CHANNEL_RESULT_ERROR; + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (pf_channel_smartcard_client_emulate(pdata->pc)) + return PF_CHANNEL_RESULT_DROP; +#endif + return PF_CHANNEL_RESULT_DROP; +} + +BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel) +{ + channel->onBackData = pf_rdpdr_back_data; + channel->onFrontData = pf_rdpdr_front_data; + + if (!pf_channel_rdpdr_server_new(ps)) + return FALSE; + if (!pf_channel_rdpdr_server_announce(ps)) + return FALSE; + + return TRUE; +} diff --git a/server/proxy/channels/pf_channel_rdpdr.h b/server/proxy/channels/pf_channel_rdpdr.h new file mode 100644 index 0000000..dbc2e75 --- /dev/null +++ b/server/proxy/channels/pf_channel_rdpdr.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak <armin.novak@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. + */ + +#ifndef FREERDP_SERVER_PROXY_RDPDR_H +#define FREERDP_SERVER_PROXY_RDPDR_H + +#include <freerdp/server/proxy/proxy_context.h> + +BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel); + +void pf_channel_rdpdr_client_free(pClientContext* pc); + +BOOL pf_channel_rdpdr_client_new(pClientContext* pc); + +BOOL pf_channel_rdpdr_client_reset(pClientContext* pc); + +BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize); + +void pf_channel_rdpdr_server_free(pServerContext* ps); + +BOOL pf_channel_rdpdr_server_new(pServerContext* ps); + +BOOL pf_channel_rdpdr_server_announce(pServerContext* ps); +BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize); + +#endif /* FREERDP_SERVER_PROXY_RDPDR_H */ diff --git a/server/proxy/channels/pf_channel_smartcard.c b/server/proxy/channels/pf_channel_smartcard.c new file mode 100644 index 0000000..1d2bdfe --- /dev/null +++ b/server/proxy/channels/pf_channel_smartcard.c @@ -0,0 +1,397 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak <armin.novak@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 <winpr/assert.h> +#include <winpr/string.h> + +#include <winpr/smartcard.h> +#include <winpr/pool.h> + +#include <freerdp/server/proxy/proxy_log.h> +#include <freerdp/emulate/scard/smartcard_emulate.h> +#include <freerdp/channels/scard.h> +#include <freerdp/channels/rdpdr.h> +#include <freerdp/utils/rdpdr_utils.h> + +#include <freerdp/utils/smartcard_operations.h> +#include <freerdp/utils/smartcard_call.h> + +#include "pf_channel_smartcard.h" +#include "pf_channel_rdpdr.h" + +#define TAG PROXY_TAG("channel.scard") + +#define SCARD_SVC_CHANNEL_NAME "SCARD" + +typedef struct +{ + InterceptContextMapEntry base; + scard_call_context* callctx; + PTP_POOL ThreadPool; + TP_CALLBACK_ENVIRON ThreadPoolEnv; + wArrayList* workObjects; +} pf_channel_client_context; + +typedef struct +{ + SMARTCARD_OPERATION op; + wStream* out; + pClientContext* pc; + wLog* log; + pf_scard_send_fkt_t send_fkt; +} pf_channel_client_queue_element; + +static pf_channel_client_context* scard_get_client_context(pClientContext* pc) +{ + pf_channel_client_context* scard = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + scard = HashTable_GetItemValue(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME); + if (!scard) + WLog_WARN(TAG, "[%s] missing in pc->interceptContextMap", SCARD_SVC_CHANNEL_NAME); + return scard; +} + +static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op, + UINT32 ioStatus) +{ + UINT16 component = 0; + UINT16 packetid = 0; + UINT32 dID = 0; + UINT32 cID = 0; + size_t pos = 0; + + WINPR_ASSERT(op); + WINPR_ASSERT(out); + + pos = Stream_GetPosition(out); + Stream_SetPosition(out, 0); + if (!Stream_CheckAndLogRequiredLength(TAG, out, 16)) + return FALSE; + + Stream_Read_UINT16(out, component); + Stream_Read_UINT16(out, packetid); + + Stream_Read_UINT32(out, dID); + Stream_Read_UINT32(out, cID); + + WINPR_ASSERT(component == RDPDR_CTYP_CORE); + WINPR_ASSERT(packetid == PAKID_CORE_DEVICE_IOCOMPLETION); + WINPR_ASSERT(dID == op->deviceID); + WINPR_ASSERT(cID == op->completionID); + + Stream_Write_UINT32(out, ioStatus); + Stream_SetPosition(out, pos); + return TRUE; +} + +struct thread_arg +{ + pf_channel_client_context* scard; + pf_channel_client_queue_element* e; +}; + +static void queue_free(void* obj); +static void* queue_copy(const void* obj); + +static VOID irp_thread(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) +{ + struct thread_arg* arg = Context; + pf_channel_client_context* scard = arg->scard; + { + UINT32 ioStatus = 0; + LONG rc = smartcard_irp_device_control_call(arg->scard->callctx, arg->e->out, &ioStatus, + &arg->e->op); + if (rc == CHANNEL_RC_OK) + { + if (pf_channel_client_write_iostatus(arg->e->out, &arg->e->op, ioStatus)) + arg->e->send_fkt(arg->e->log, arg->e->pc, arg->e->out); + } + } + queue_free(arg->e); + free(arg); + ArrayList_Remove(scard->workObjects, Work); +} + +static BOOL start_irp_thread(pf_channel_client_context* scard, + const pf_channel_client_queue_element* e) +{ + PTP_WORK work = NULL; + struct thread_arg* arg = calloc(1, sizeof(struct thread_arg)); + if (!arg) + return FALSE; + arg->scard = scard; + arg->e = queue_copy(e); + if (!arg->e) + goto fail; + + work = CreateThreadpoolWork(irp_thread, arg, &scard->ThreadPoolEnv); + if (!work) + goto fail; + ArrayList_Append(scard->workObjects, work); + SubmitThreadpoolWork(work); + + return TRUE; + +fail: + if (arg) + queue_free(arg->e); + free(arg); + return FALSE; +} + +BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out, + pf_scard_send_fkt_t send_fkt) +{ + BOOL rc = FALSE; + LONG status = 0; + UINT32 FileId = 0; + UINT32 CompletionId = 0; + UINT32 ioStatus = 0; + pf_channel_client_queue_element e = { 0 }; + pf_channel_client_context* scard = scard_get_client_context(pc); + + WINPR_ASSERT(log); + WINPR_ASSERT(send_fkt); + WINPR_ASSERT(s); + + if (!scard) + return FALSE; + + e.log = log; + e.pc = pc; + e.out = out; + e.send_fkt = send_fkt; + + /* Skip IRP header */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return FALSE; + else + { + UINT32 DeviceId = 0; + UINT32 MajorFunction = 0; + UINT32 MinorFunction = 0; + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */ + Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */ + Stream_Read_UINT32(s, MajorFunction); /* MajorFunction (4 bytes) */ + Stream_Read_UINT32(s, MinorFunction); /* MinorFunction (4 bytes) */ + + if (MajorFunction != IRP_MJ_DEVICE_CONTROL) + { + WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %2", SCARD_SVC_CHANNEL_NAME, + rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL), rdpdr_irp_string(MajorFunction)); + return FALSE; + } + e.op.completionID = CompletionId; + e.op.deviceID = DeviceId; + + if (!rdpdr_write_iocompletion_header(out, DeviceId, CompletionId, 0)) + return FALSE; + } + + status = smartcard_irp_device_control_decode(s, CompletionId, FileId, &e.op); + if (status != 0) + goto fail; + + switch (e.op.ioControlCode) + { + case SCARD_IOCTL_LISTREADERGROUPSA: + case SCARD_IOCTL_LISTREADERGROUPSW: + case SCARD_IOCTL_LISTREADERSA: + case SCARD_IOCTL_LISTREADERSW: + case SCARD_IOCTL_LOCATECARDSA: + case SCARD_IOCTL_LOCATECARDSW: + case SCARD_IOCTL_LOCATECARDSBYATRA: + case SCARD_IOCTL_LOCATECARDSBYATRW: + case SCARD_IOCTL_GETSTATUSCHANGEA: + case SCARD_IOCTL_GETSTATUSCHANGEW: + case SCARD_IOCTL_CONNECTA: + case SCARD_IOCTL_CONNECTW: + case SCARD_IOCTL_RECONNECT: + case SCARD_IOCTL_DISCONNECT: + case SCARD_IOCTL_BEGINTRANSACTION: + case SCARD_IOCTL_ENDTRANSACTION: + case SCARD_IOCTL_STATE: + case SCARD_IOCTL_STATUSA: + case SCARD_IOCTL_STATUSW: + case SCARD_IOCTL_TRANSMIT: + case SCARD_IOCTL_CONTROL: + case SCARD_IOCTL_GETATTRIB: + case SCARD_IOCTL_SETATTRIB: + if (!start_irp_thread(scard, &e)) + goto fail; + return TRUE; + + default: + status = smartcard_irp_device_control_call(scard->callctx, out, &ioStatus, &e.op); + if (status != 0) + goto fail; + if (!pf_channel_client_write_iostatus(out, &e.op, ioStatus)) + goto fail; + break; + } + + rc = send_fkt(log, pc, out) == CHANNEL_RC_OK; + +fail: + smartcard_operation_free(&e.op, FALSE); + return rc; +} + +BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s) +{ + WLog_ERR(TAG, "TODO: unimplemented"); + return TRUE; +} + +static void channel_stop_and_wait(pf_channel_client_context* scard, BOOL reset) +{ + WINPR_ASSERT(scard); + smartcard_call_context_signal_stop(scard->callctx, FALSE); + + while (ArrayList_Count(scard->workObjects) > 0) + { + PTP_WORK work = ArrayList_GetItem(scard->workObjects, 0); + if (!work) + continue; + WaitForThreadpoolWorkCallbacks(work, TRUE); + } + + smartcard_call_context_signal_stop(scard->callctx, reset); +} + +static void pf_channel_scard_client_context_free(InterceptContextMapEntry* base) +{ + pf_channel_client_context* entry = (pf_channel_client_context*)base; + if (!entry) + return; + + /* Set the stop event. + * All threads waiting in blocking operations will abort at the next + * available polling slot */ + channel_stop_and_wait(entry, FALSE); + ArrayList_Free(entry->workObjects); + CloseThreadpool(entry->ThreadPool); + DestroyThreadpoolEnvironment(&entry->ThreadPoolEnv); + + smartcard_call_context_free(entry->callctx); + free(entry); +} + +static void queue_free(void* obj) +{ + pf_channel_client_queue_element* element = obj; + if (!element) + return; + smartcard_operation_free(&element->op, FALSE); + Stream_Free(element->out, TRUE); + free(element); +} + +static void* queue_copy(const void* obj) +{ + const pf_channel_client_queue_element* other = obj; + pf_channel_client_queue_element* copy = NULL; + if (!other) + return NULL; + copy = calloc(1, sizeof(pf_channel_client_queue_element)); + if (!copy) + return NULL; + + *copy = *other; + copy->out = Stream_New(NULL, Stream_Capacity(other->out)); + if (!copy->out) + goto fail; + Stream_Write(copy->out, Stream_Buffer(other->out), Stream_GetPosition(other->out)); + return copy; +fail: + queue_free(copy); + return NULL; +} + +static void work_object_free(void* arg) +{ + PTP_WORK work = arg; + CloseThreadpoolWork(work); +} + +BOOL pf_channel_smartcard_client_new(pClientContext* pc) +{ + pf_channel_client_context* scard = NULL; + wObject* obj = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + scard = calloc(1, sizeof(pf_channel_client_context)); + if (!scard) + return FALSE; + scard->base.free = pf_channel_scard_client_context_free; + scard->callctx = smartcard_call_context_new(pc->context.settings); + if (!scard->callctx) + goto fail; + + scard->workObjects = ArrayList_New(TRUE); + if (!scard->workObjects) + goto fail; + obj = ArrayList_Object(scard->workObjects); + WINPR_ASSERT(obj); + obj->fnObjectFree = work_object_free; + + scard->ThreadPool = CreateThreadpool(NULL); + if (!scard->ThreadPool) + goto fail; + InitializeThreadpoolEnvironment(&scard->ThreadPoolEnv); + SetThreadpoolCallbackPool(&scard->ThreadPoolEnv, scard->ThreadPool); + + return HashTable_Insert(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME, scard); +fail: + pf_channel_scard_client_context_free(&scard->base); + return FALSE; +} + +void pf_channel_smartcard_client_free(pClientContext* pc) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + HashTable_Remove(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME); +} + +BOOL pf_channel_smartcard_client_emulate(pClientContext* pc) +{ + pf_channel_client_context* scard = scard_get_client_context(pc); + if (!scard) + return FALSE; + return smartcard_call_is_configured(scard->callctx); +} + +BOOL pf_channel_smartcard_client_reset(pClientContext* pc) +{ + pf_channel_client_context* scard = scard_get_client_context(pc); + if (!scard) + return TRUE; + + channel_stop_and_wait(scard, TRUE); + return TRUE; +} diff --git a/server/proxy/channels/pf_channel_smartcard.h b/server/proxy/channels/pf_channel_smartcard.h new file mode 100644 index 0000000..975636d --- /dev/null +++ b/server/proxy/channels/pf_channel_smartcard.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak <armin.novak@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. + */ + +#ifndef FREERDP_SERVER_PROXY_SCARD_H +#define FREERDP_SERVER_PROXY_SCARD_H + +#include <winpr/wlog.h> +#include <freerdp/server/proxy/proxy_context.h> + +typedef UINT (*pf_scard_send_fkt_t)(wLog* log, pClientContext*, wStream*); + +BOOL pf_channel_smartcard_client_new(pClientContext* pc); +void pf_channel_smartcard_client_free(pClientContext* pc); + +BOOL pf_channel_smartcard_client_reset(pClientContext* pc); +BOOL pf_channel_smartcard_client_emulate(pClientContext* pc); + +BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out, + pf_scard_send_fkt_t fkt); +BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s); + +#endif /* FREERDP_SERVER_PROXY_SCARD_H */ diff --git a/server/proxy/cli/CMakeLists.txt b/server/proxy/cli/CMakeLists.txt new file mode 100644 index 0000000..1416b4a --- /dev/null +++ b/server/proxy/cli/CMakeLists.txt @@ -0,0 +1,60 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Proxy Server +# +# Copyright 2021 Armin Novak <armin.novak@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. + +set(PROXY_APP_SRCS freerdp_proxy.c) + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + + configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +set(APP_NAME "freerdp-proxy") +add_executable(${APP_NAME} + ${PROXY_APP_SRCS} +) + +set(MANPAGE_NAME ${APP_NAME}.1) +if (WITH_BINARY_VERSIONING) + set_target_properties(${APP_NAME} + PROPERTIES + OUTPUT_NAME "${APP_NAME}${FREERDP_API_VERSION}" + ) + set(MANPAGE_NAME ${APP_NAME}${FREERDP_API_VERSION}.1) +endif() + +target_link_libraries(${APP_NAME} ${MODULE_NAME}) +install(TARGETS ${APP_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server) +if (WITH_DEBUG_SYMBOLS AND MSVC) + install(FILES ${CMAKE_PDB_BINARY_DIR}/${APP_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${APP_NAME} PROPERTY FOLDER "Server/proxy") + +configure_file(${APP_NAME}.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}) +install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME} 1) diff --git a/server/proxy/cli/freerdp-proxy.1.in b/server/proxy/cli/freerdp-proxy.1.in new file mode 100644 index 0000000..c41f97c --- /dev/null +++ b/server/proxy/cli/freerdp-proxy.1.in @@ -0,0 +1,85 @@ +.de URL +\\$2 \(laURL: \\$1 \(ra\\$3 +.. +.if \n[.g] .mso www.tmac +.TH @MANPAGE_NAME@ 1 2023-12-14 "@FREERDP_VERSION_FULL@" "FreeRDP" +.SH NAME +@MANPAGE_NAME@ \- A server binary allowing MITM proxying of RDP connections +.SH SYNOPSIS +.B @MANPAGE_NAME@ +[\fB-h\fP] +[\fB--help\fP] +[\fB--buildconfig\fP] +[\fB--dump-config\fP \fB<config file>\fP] +[\fB-v\fP] +[\fB--version\fP] +[\fB<config file>\fP] +.SH DESCRIPTION +.B @MANPAGE_NAME@ +can be used to proxy a RDP connection between a target server and connecting clients. +Possible usage scenarios are: +.IP Proxying +Connect outdated/insecure RDP servers from behind a (more secure) proxy +.IP Analysis +Allow detailed protocol analysis of (many) unknown protocol features (channels) +.IP Inspection +MITM proxy for session inspection and recording + +.SH OPTIONS +.IP -h,--help +Display a help text explaining usage. +.IP --buildconfig +Print the build configuration of the proxy and exit. +.IP -v,--version +Print the version of the proxy and exit. +.IP --dump-config \fB<config-ini-file>\fP +Dump a template configuration to \fB<config-ini-file>\fP +.IP \fB<config-ini-file>\fP +Start the proxy with settings read from \fB<config-ini-file>\fP + +.SH WARNING +The proxy does not support authentication out of the box but acts simply as intermediary. +Only \fBRDP\fP and \fBTLS\fP security modes are supported, \fBNLA\fP will fail for connections to the proxy. +To implement authentication a \fBproxy-module\fP can be implemented that can authenticate against some backend +and map connecting users and credentials to target server users and credentials. + +.SH EXAMPLES +@MANPAGE_NAME@ /some/config/file + +@MANPAGE_NAME@ --dump-config /some/config/file + +.SH PREPARATIONS + +1. generate certificates for proxy + +\fBwinpr-makecert -rdp -path . proxy\fP + +2. generate proxy configuration + +\fB@MANPAGE_NAME@ --dump-config proxy.ini\fP + +3. edit configurartion and: + + * provide (preferrably absolute) paths for \fBCertificateFile\fP and \fBPrivateKeyFile\fP generated previously + * remove the \fBCertificateContents\fP and \fBPrivateKeyContents\fP + * Adjust the \fB[Server]\fP settings \fBHost\fP and \fBPort\fP to bind a specific port on a network interface + * Adjust the \fB[Target]\fP \fBHost\fP and \fBPort\fP settings to the \fBRDP\fP target server + * Adjust (or remove if unuse) the \fBPlugins\fP settings + +3. start proxy server + + \fB@MANPAGE_NAME@ proxy.ini\fP + +.SH EXIT STATUS +.TP +.B 0 +Successful program execution. +.TP +.B 1 +Otherwise. + +.SH SEE ALSO +wlog(7) + +.SH AUTHOR +FreeRDP <team@freerdp.com> diff --git a/server/proxy/cli/freerdp_proxy.c b/server/proxy/cli/freerdp_proxy.c new file mode 100644 index 0000000..bc53ae2 --- /dev/null +++ b/server/proxy/cli/freerdp_proxy.c @@ -0,0 +1,161 @@ +/** + * 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> + * + * 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/collections.h> + +#include <freerdp/version.h> +#include <freerdp/freerdp.h> + +#include <freerdp/server/proxy/proxy_server.h> +#include <freerdp/server/proxy/proxy_log.h> + +#include <stdlib.h> +#include <signal.h> + +#define TAG PROXY_TAG("server") + +static proxyServer* server = NULL; + +#if defined(_WIN32) +static const char* strsignal(int signum) +{ + switch (signum) + { + case SIGINT: + return "SIGINT"; + case SIGTERM: + return "SIGTERM"; + default: + return "UNKNOWN"; + } +} +#endif + +static void cleanup_handler(int signum) +{ + printf("\n"); + WLog_INFO(TAG, "caught signal %s [%d], starting cleanup...", strsignal(signum), signum); + + WLog_INFO(TAG, "stopping all connections."); + pf_server_stop(server); +} + +static void pf_server_register_signal_handlers(void) +{ + signal(SIGINT, cleanup_handler); + signal(SIGTERM, cleanup_handler); +#ifndef _WIN32 + signal(SIGQUIT, cleanup_handler); + signal(SIGKILL, cleanup_handler); +#endif +} + +static WINPR_NORETURN(void usage(const char* app)) +{ + printf("Usage:\n"); + printf("%s -h Display this help text.\n", app); + printf("%s --help Display this help text.\n", app); + printf("%s --buildconfig Print the build configuration.\n", app); + printf("%s <config ini file> Start the proxy with <config.ini>\n", app); + printf("%s --dump-config <config ini file> Create a template <config.ini>\n", app); + printf("%s -v Print out binary version.\n", app); + printf("%s --version Print out binary version.\n", app); + exit(0); +} + +static void version(const char* app) +{ + printf("%s version %s", app, freerdp_get_version_string()); + exit(0); +} + +static WINPR_NORETURN(void buildconfig(const char* app)) +{ + printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION); + printf("%s", freerdp_get_build_config()); + exit(0); +} + +int main(int argc, char* argv[]) +{ + proxyConfig* config = NULL; + char* config_path = "config.ini"; + int status = -1; + + pf_server_register_signal_handlers(); + + WLog_INFO(TAG, "freerdp-proxy version info:"); + WLog_INFO(TAG, "\tFreeRDP version: %s", FREERDP_VERSION_FULL); + WLog_INFO(TAG, "\tGit commit: %s", FREERDP_GIT_REVISION); + WLog_DBG(TAG, "\tBuild config: %s", freerdp_get_build_config()); + + if (argc < 2) + usage(argv[0]); + + { + const char* arg = argv[1]; + + if (_stricmp(arg, "-h") == 0) + usage(argv[0]); + else if (_stricmp(arg, "--help") == 0) + usage(argv[0]); + else if (_stricmp(arg, "--buildconfig") == 0) + buildconfig(argv[0]); + else if (_stricmp(arg, "--dump-config") == 0) + { + if (argc <= 2) + usage(argv[0]); + pf_server_config_dump(argv[2]); + status = 0; + goto fail; + } + else if (_stricmp(arg, "-v") == 0) + version(argv[0]); + else if (_stricmp(arg, "--version") == 0) + version(argv[0]); + config_path = argv[1]; + } + + config = pf_server_config_load_file(config_path); + if (!config) + goto fail; + + pf_server_config_print(config); + + server = pf_server_new(config); + pf_server_config_free(config); + + if (!server) + goto fail; + + if (!pf_server_start(server)) + goto fail; + + if (!pf_server_run(server)) + goto fail; + + status = 0; + +fail: + pf_server_free(server); + + return status; +} diff --git a/server/proxy/config.ini b/server/proxy/config.ini new file mode 100644 index 0000000..a8ac44b --- /dev/null +++ b/server/proxy/config.ini @@ -0,0 +1,53 @@ +[Server] +Host = 0.0.0.0 +Port = 3389 + +[Target] +; If this value is set to TRUE, the target server info will be parsed using the +; load balance info setting at runtime. The format is +; "Cookie: msts=<target server>", and can be set in an rdp file for windows/mac, +; and the /load-balance-info: CLI option for xfreerdp. Otherwise, the server +; will always connect to the same target, using the configured values of `Host` +; and `Port`. +FixedTarget = TRUE +Host = CustomHost +Port = 3389 + +[Input] +Mouse = TRUE +Keyboard = TRUE + +[Security] +ServerTlsSecurity = TRUE +ServerRdpSecurity = FALSE +ClientTlsSecurity = TRUE +ClientRdpSecurity = FALSE +ClientNlaSecurity = TRUE +ClientAllowFallbackToTls = TRUE + +[Channels] +GFX = TRUE +DisplayControl = TRUE +Clipboard = TRUE +AudioOutput = TRUE +RemoteApp = TRUE +; a list of comma seperated static channels that will be proxied. This feature is useful, +; for example when there's a custom static channel that isn't implemented in freerdp/proxy, and is needed to be proxied when connecting through the proxy. +; Passthrough = "" + +[Clipboard] +TextOnly = FALSE +MaxTextLength = 10 # 0 for no limit. + +[GFXSettings] +DecodeGFX = TRUE + +[Plugins] +; An optional, comma separated list of paths to modules that the proxy should load at startup. +; +; Modules = "proxy-demo-plugin.so" + +; An optional, comma separated list of required plugins (names), +; that the proxy won't start without having them loaded. +; +; Required = "demo" diff --git a/server/proxy/freerdp-proxy.pc.in b/server/proxy/freerdp-proxy.pc.in new file mode 100644 index 0000000..4655075 --- /dev/null +++ b/server/proxy/freerdp-proxy.pc.in @@ -0,0 +1,16 @@ +prefix=@PKG_CONFIG_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@FREERDP_INCLUDE_DIR@ +libs=-lfreerdp-server-proxy@FREERDP_API_VERSION@ + +Name: FreeRDP proxy +Description: FreeRDP: A Remote Desktop Protocol Implementation +URL: http://www.freerdp.com/ +Version: @FREERDP_VERSION@ +Requires: +Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_VERSION_MAJOR@ freerdp-server@FREERDP_VERSION_MAJOR@ freerdp-client@FREERDP_VERSION_MAJOR@ + +Libs: -L${libdir} ${libs} +Libs.private: -ldl -lpthread +Cflags: -I${includedir} diff --git a/server/proxy/modules/CMakeLists.txt b/server/proxy/modules/CMakeLists.txt new file mode 100644 index 0000000..500a3e7 --- /dev/null +++ b/server/proxy/modules/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.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. + +# The third-party directory is meant for third-party components to be built +# as part of the main FreeRDP build system, making separate maintenance easier. +# Subdirectories of the third-party directory are ignored by git, but are +# automatically included by CMake when the -DWITH_THIRD_PARTY=on option is used. + +# include proxy header files for proxy modules +include_directories("${PROJECT_SOURCE_DIR}/server/proxy") +include_directories("${PROJECT_SOURCE_DIR}/server/proxy/modules") + +# taken from FreeRDP/third-party/CMakeLists.txt +file(GLOB all_valid_subdirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/CMakeLists.txt") + +foreach(dir ${all_valid_subdirs}) + if(${dir} MATCHES "^([^/]*)/+CMakeLists.txt") + string(REGEX REPLACE "^([^/]*)/+CMakeLists.txt" "\\1" dir_trimmed ${dir}) + message(STATUS "Adding proxy module ${dir_trimmed}") + add_subdirectory(${dir_trimmed}) + endif() +endforeach(dir) diff --git a/server/proxy/modules/README.md b/server/proxy/modules/README.md new file mode 100644 index 0000000..f1d2206 --- /dev/null +++ b/server/proxy/modules/README.md @@ -0,0 +1,66 @@ +# Proxy module API + +`freerdp-proxy` has an API for hooking/filtering certain events/messages. +A module can register callbacks to events, allowing to record the data and control whether to pass/ignore, or right out drop the connection. + +During startup, the proxy reads its modules from the configuration: + +```ini +[Plugins] +Modules = demo,cap +``` + +These modules are loaded in a best effort manner. Additionally there is a configuration field for modules that must be loaded, +so the proxy refuses to start if they are not found: + +```ini +[Plugins] +Required = demo,cap +``` + +Modules must be installed as shared libraris in the `<base install>/lib/freerdp3/proxy` folder and match the pattern +`proxy-<name>-plugin.<ext>` (e.g. `proxy-demo-plugin.so`) to be found. +For security reasons loading by full path is not supported and only the installation path is used for lookup. + +## Currently supported hook events + +### Client + +* ClientInitConnect: Called before the client tries to open a connection +* ClientUninitConnect: Called after the client has disconnected +* ClientPreConnect: Called in client PreConnect callback +* ClientPostConnect: Called in client PostConnect callback +* ClientPostDisconnect: Called in client PostDisconnect callback +* ClientX509Certificate: Called in client X509 certificate verification callback +* ClientLoginFailure: Called in client login failure callback +* ClientEndPaint: Called in client EndPaint callback + +### Server + +* ServerPostConnect: Called after a client has connected +* ServerPeerActivate: Called after a client has activated +* ServerChannelsInit: Called after channels are initialized +* ServerChannelsFree: Called after channels are cleaned up +* ServerSessionEnd: Called after the client connection disconnected + +## Currently supported filter events + +* KeyboardEvent: Keyboard event, e.g. all key press and release events +* MouseEvent: Mouse event, e.g. mouse movement and button press/release events +* ClientChannelData: Client static channel data +* ServerChannelData: Server static channel data +* DynamicChannelCreate: Dynamic channel create +* ServerFetchTargetAddr: Fetch target address (e.g. RDP TargetInfo) +* ServerPeerLogon: A peer is logging on + +## Developing a new module +* Create a new file that includes `freerdp/server/proxy/proxy_modules_api.h`. +* Implement the `proxy_module_entry_point` function and register the callbacks you are interested in. +* Each callback receives two parameters: + * `connectionInfo* info` holds connection info of the raised event. + * `void* param` holds the actual event data. It should be casted by the filter to the suitable struct from `filters_api.h`. +* Each callback must return a `BOOL`: + * `FALSE`: The event will not be proxied. + * `TRUE`: The event will be proxied. + +A demo can be found in `filter_demo.c`. diff --git a/server/proxy/modules/bitmap-filter/CMakeLists.txt b/server/proxy/modules/bitmap-filter/CMakeLists.txt new file mode 100644 index 0000000..d2cc03b --- /dev/null +++ b/server/proxy/modules/bitmap-filter/CMakeLists.txt @@ -0,0 +1,60 @@ +# +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Proxy Server Demo C++ Module +# +# Copyright 2019 Kobi Mizrachi <kmizrachi18@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. +# + +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if (NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(proxy-bitmap-filter-plugin + VERSION ${FREERDP_DEFAULT_PROJECT_VERSION} + LANGUAGES CXX +) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +project(proxy-bitmap-filter-plugin + VERSION ${FREERDP_DEFAULT_PROJECT_VERSION} + LANGUAGES CXX +) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/) +include(CommonConfigOptions) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(${PROJECT_NAME} SHARED + bitmap-filter.cpp +) + +target_link_libraries(${PROJECT_NAME} winpr freerdp) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1) + +install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR}) diff --git a/server/proxy/modules/bitmap-filter/bitmap-filter.cpp b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp new file mode 100644 index 0000000..d1b2e78 --- /dev/null +++ b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp @@ -0,0 +1,453 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server persist-bitmap-filter Module + * + * this module is designed to deactivate all persistent bitmap cache settings a + * client might send. + * + * Copyright 2023 Armin Novak <anovak@thincast.com> + * Copyright 2023 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 <iostream> +#include <vector> +#include <string> +#include <algorithm> +#include <map> +#include <memory> +#include <mutex> + +#include <freerdp/server/proxy/proxy_modules_api.h> +#include <freerdp/server/proxy/proxy_context.h> + +#include <freerdp/channels/drdynvc.h> +#include <freerdp/channels/rdpgfx.h> +#include <freerdp/utils/gfx.h> + +#define TAG MODULE_TAG("persist-bitmap-filter") + +static constexpr char plugin_name[] = "bitmap-filter"; +static constexpr char plugin_desc[] = + "this plugin deactivates and filters persistent bitmap cache."; + +static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME }; +static const std::vector<std::string> plugin_dyn_intercept = { RDPGFX_DVC_CHANNEL_NAME }; + +class DynChannelState +{ + + public: + bool skip() const + { + return _toSkip != 0; + } + + bool skip(size_t s) + { + if (s > _toSkip) + _toSkip = 0; + else + _toSkip -= s; + return skip(); + } + + size_t remaining() const + { + return _toSkip; + } + + size_t total() const + { + return _totalSkipSize; + } + + void setSkipSize(size_t len) + { + _toSkip = _totalSkipSize = len; + } + + bool drop() const + { + return _drop; + } + + void setDrop(bool d) + { + _drop = d; + } + + uint32_t channelId() const + { + return _channelId; + } + + void setChannelId(uint32_t id) + { + _channelId = id; + } + + private: + size_t _toSkip = 0; + size_t _totalSkipSize = 0; + bool _drop = false; + uint32_t _channelId = 0; +}; + +static BOOL filter_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(pdata->pc); + WINPR_ASSERT(custom); + + auto settings = pdata->pc->context.settings; + + /* We do not want persistent bitmap cache to be used with proxy */ + return freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, FALSE); +} + +static BOOL filter_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + auto intercept = std::find(plugin_dyn_intercept.begin(), plugin_dyn_intercept.end(), + data->name) != plugin_dyn_intercept.end(); + if (intercept) + data->intercept = TRUE; + return TRUE; +} + +static BOOL filter_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(), + data->name) != plugin_static_intercept.end(); + if (intercept) + data->intercept = TRUE; + return TRUE; +} + +static size_t drdynvc_cblen_to_bytes(UINT8 cbLen) +{ + switch (cbLen) + { + case 0: + return 1; + + case 1: + return 2; + + default: + return 4; + } +} + +static UINT32 drdynvc_read_variable_uint(wStream* s, UINT8 cbLen) +{ + UINT32 val = 0; + + switch (cbLen) + { + case 0: + Stream_Read_UINT8(s, val); + break; + + case 1: + Stream_Read_UINT16(s, val); + break; + + default: + Stream_Read_UINT32(s, val); + break; + } + + return val; +} + +static BOOL drdynvc_try_read_header(wStream* s, size_t& channelId, size_t& length) +{ + UINT8 value = 0; + Stream_SetPosition(s, 0); + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + Stream_Read_UINT8(s, value); + + const UINT8 cmd = (value & 0xf0) >> 4; + const UINT8 Sp = (value & 0x0c) >> 2; + const UINT8 cbChId = (value & 0x03); + + switch (cmd) + { + case DATA_PDU: + case DATA_FIRST_PDU: + break; + default: + return FALSE; + } + + const size_t channelIdLen = drdynvc_cblen_to_bytes(cbChId); + if (Stream_GetRemainingLength(s) < channelIdLen) + return FALSE; + + channelId = drdynvc_read_variable_uint(s, cbChId); + length = Stream_Length(s); + if (cmd == DATA_FIRST_PDU) + { + const size_t dataLen = drdynvc_cblen_to_bytes(Sp); + if (Stream_GetRemainingLength(s) < dataLen) + return FALSE; + + length = drdynvc_read_variable_uint(s, Sp); + } + + return TRUE; +} + +static DynChannelState* filter_get_plugin_data(proxyPlugin* plugin, proxyData* pdata) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto mgr = static_cast<proxyPluginsManager*>(plugin->custom); + WINPR_ASSERT(mgr); + + WINPR_ASSERT(mgr->GetPluginData); + return static_cast<DynChannelState*>(mgr->GetPluginData(mgr, plugin_name, pdata)); +} + +static BOOL filter_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, DynChannelState* data) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto mgr = static_cast<proxyPluginsManager*>(plugin->custom); + WINPR_ASSERT(mgr); + + WINPR_ASSERT(mgr->SetPluginData); + return mgr->SetPluginData(mgr, plugin_name, pdata, data); +} + +static UINT8 drdynvc_value_to_cblen(UINT32 value) +{ + if (value <= 0xFF) + return 0; + if (value <= 0xFFFF) + return 1; + return 2; +} + +static BOOL drdynvc_write_variable_uint(wStream* s, UINT32 value, UINT8 cbLen) +{ + switch (cbLen) + { + case 0: + Stream_Write_UINT8(s, static_cast<UINT8>(value)); + break; + + case 1: + Stream_Write_UINT16(s, static_cast<UINT16>(value)); + break; + + default: + Stream_Write_UINT32(s, value); + break; + } + + return TRUE; +} + +static BOOL drdynvc_write_header(wStream* s, UINT32 channelId) +{ + const UINT8 cbChId = drdynvc_value_to_cblen(channelId); + const UINT8 value = (DATA_PDU << 4) | cbChId; + const size_t len = drdynvc_cblen_to_bytes(cbChId) + 1; + + if (!Stream_EnsureRemainingCapacity(s, len)) + return FALSE; + + Stream_Write_UINT8(s, value); + return drdynvc_write_variable_uint(s, value, cbChId); +} + +static BOOL filter_forward_empty_offer(const char* sessionID, proxyDynChannelInterceptData* data, + size_t startPosition, UINT32 channelId) +{ + WINPR_ASSERT(data); + + Stream_SetPosition(data->data, startPosition); + if (!drdynvc_write_header(data->data, channelId)) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(data->data, sizeof(UINT16))) + return FALSE; + Stream_Write_UINT16(data->data, 0); + Stream_SealLength(data->data); + + WLog_INFO(TAG, "[SessionID=%s][%s] forwarding empty %s", sessionID, plugin_name, + rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER)); + data->rewritten = TRUE; + return TRUE; +} + +static BOOL filter_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyDynChannelInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + data->result = PF_CHANNEL_RESULT_PASS; + if (!data->isBackData && + (strncmp(data->name, RDPGFX_DVC_CHANNEL_NAME, ARRAYSIZE(RDPGFX_DVC_CHANNEL_NAME)) == 0)) + { + auto state = filter_get_plugin_data(plugin, pdata); + if (!state) + { + WLog_ERR(TAG, "[SessionID=%s][%s] missing custom data, aborting!", pdata->session_id, + plugin_name); + return FALSE; + } + const size_t inputDataLength = Stream_Length(data->data); + UINT16 cmdId = RDPGFX_CMDID_UNUSED_0000; + + const auto pos = Stream_GetPosition(data->data); + if (!state->skip()) + { + if (data->first) + { + size_t channelId = 0; + size_t length = 0; + if (drdynvc_try_read_header(data->data, channelId, length)) + { + if (Stream_GetRemainingLength(data->data) >= 2) + { + Stream_Read_UINT16(data->data, cmdId); + state->setSkipSize(length); + state->setDrop(false); + } + } + + switch (cmdId) + { + case RDPGFX_CMDID_CACHEIMPORTOFFER: + state->setDrop(true); + state->setChannelId(channelId); + break; + default: + break; + } + Stream_SetPosition(data->data, pos); + } + } + + if (state->skip()) + { + state->skip(inputDataLength); + if (state->drop()) + { + WLog_WARN(TAG, + "[SessionID=%s][%s] dropping %s packet [total:%" PRIuz ", current:%" PRIuz + ", remaining: %" PRIuz "]", + pdata->session_id, plugin_name, + rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER), state->total(), + inputDataLength, state->remaining()); + data->result = PF_CHANNEL_RESULT_DROP; + +#if 0 // TODO: Sending this does screw up some windows RDP server versions :/ + if (state->remaining() == 0) + { + if (!filter_forward_empty_offer(pdata->session_id, data, pos, + state->channelId())) + return FALSE; + } +#endif + } + } + } + + return TRUE; +} + +static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata, void*) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto state = filter_get_plugin_data(plugin, pdata); + delete state; + + auto newstate = new DynChannelState(); + if (!filter_set_plugin_data(plugin, pdata, newstate)) + { + delete newstate; + return FALSE; + } + + return TRUE; +} + +static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata, void*) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto state = filter_get_plugin_data(plugin, pdata); + delete state; + filter_set_plugin_data(plugin, pdata, nullptr); + return TRUE; +} + +#ifdef __cplusplus +extern "C" +{ +#endif + FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata); +#ifdef __cplusplus +} +#endif + +BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata) +{ + proxyPlugin plugin = {}; + + plugin.name = plugin_name; + plugin.description = plugin_desc; + + plugin.ServerSessionStarted = filter_server_session_started; + plugin.ServerSessionEnd = filter_server_session_end; + + plugin.ClientPreConnect = filter_client_pre_connect; + + plugin.StaticChannelToIntercept = filter_static_channel_intercept_list; + plugin.DynChannelToIntercept = filter_dyn_channel_intercept_list; + plugin.DynChannelIntercept = filter_dyn_channel_intercept; + + plugin.custom = plugins_manager; + if (!plugin.custom) + return FALSE; + plugin.userdata = userdata; + + return plugins_manager->RegisterPlugin(plugins_manager, &plugin); +} diff --git a/server/proxy/modules/demo/CMakeLists.txt b/server/proxy/modules/demo/CMakeLists.txt new file mode 100644 index 0000000..bdd85a3 --- /dev/null +++ b/server/proxy/modules/demo/CMakeLists.txt @@ -0,0 +1,53 @@ +# +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Proxy Server Demo C++ Module +# +# Copyright 2019 Kobi Mizrachi <kmizrachi18@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. +# + +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if (NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(proxy-demo-plugin + VERSION ${FREERDP_DEFAULT_PROJECT_VERSION} + LANGUAGES CXX +) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/) +include(CommonConfigOptions) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(${PROJECT_NAME} SHARED + demo.cpp +) + +target_link_libraries(${PROJECT_NAME} winpr) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1) + +install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR}) diff --git a/server/proxy/modules/demo/demo.cpp b/server/proxy/modules/demo/demo.cpp new file mode 100644 index 0000000..75526ef --- /dev/null +++ b/server/proxy/modules/demo/demo.cpp @@ -0,0 +1,422 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server Demo C++ Module + * + * Copyright 2019 Kobi Mizrachi <kmizrachi18@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 <iostream> + +#include <freerdp/api.h> +#include <freerdp/scancode.h> +#include <freerdp/server/proxy/proxy_modules_api.h> + +#define TAG MODULE_TAG("demo") + +struct demo_custom_data +{ + proxyPluginsManager* mgr; + int somesetting; +}; + +static constexpr char plugin_name[] = "demo"; +static constexpr char plugin_desc[] = "this is a test plugin"; + +static BOOL demo_plugin_unload(proxyPlugin* plugin) +{ + WINPR_ASSERT(plugin); + + std::cout << "C++ demo plugin: unloading..." << std::endl; + + /* Here we have to free up our custom data storage. */ + if (plugin) + delete static_cast<struct demo_custom_data*>(plugin->custom); + + return TRUE; +} + +static BOOL demo_client_init_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_uninit_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_post_disconnect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_x509_certificate(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_login_failure(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_end_paint(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_redirect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_peer_activate(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_channels_init(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_channels_free(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_session_end(proxyPlugin* plugin, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(custom); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_filter_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + proxyPluginsManager* mgr = nullptr; + auto event_data = static_cast<const proxyKeyboardEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + mgr = plugin->mgr; + WINPR_ASSERT(mgr); + + if (event_data == nullptr) + return FALSE; + + if (event_data->rdp_scan_code == RDP_SCANCODE_KEY_B) + { + /* user typed 'B', that means bye :) */ + std::cout << "C++ demo plugin: aborting connection" << std::endl; + mgr->AbortConnect(mgr, pdata); + } + + return TRUE; +} + +static BOOL demo_filter_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + proxyPluginsManager* mgr = nullptr; + auto event_data = static_cast<const proxyUnicodeEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + mgr = plugin->mgr; + WINPR_ASSERT(mgr); + + if (event_data == nullptr) + return FALSE; + + if (event_data->code == 'b') + { + /* user typed 'B', that means bye :) */ + std::cout << "C++ demo plugin: aborting connection" << std::endl; + mgr->AbortConnect(mgr, pdata); + } + + return TRUE; +} + +static BOOL demo_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + auto event_data = static_cast<const proxyMouseEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + auto event_data = static_cast<const proxyMouseExEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + WLog_INFO(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id, + channel->data_len); + return TRUE; +} + +static BOOL demo_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + WLog_WARN(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id, + channel->data_len); + return TRUE; +} + +static BOOL demo_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + WLog_WARN(TAG, "%s [0x%04" PRIx16 "]", channel->channel_name, channel->channel_id); + return TRUE; +} + +static BOOL demo_server_fetch_target_addr(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + auto event_data = static_cast<const proxyFetchTargetEventInfo*>(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WLog_INFO(TAG, "called"); + return TRUE; +} + +static BOOL demo_server_peer_logon(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + auto info = static_cast<const proxyServerPeerLogon*>(param); + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(info); + WINPR_ASSERT(info->identity); + + WLog_INFO(TAG, "%d", info->automatic); + return TRUE; +} + +static BOOL demo_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + WLog_INFO(TAG, "%s", __func__); + return TRUE; +} + +static BOOL demo_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + WLog_INFO(TAG, "%s", __func__); + return TRUE; +} + +static BOOL demo_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyDynChannelInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + WLog_INFO(TAG, "%s", __func__); + return TRUE; +} + +#ifdef __cplusplus +extern "C" +{ +#endif + FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata); +#ifdef __cplusplus +} +#endif + +BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata) +{ + struct demo_custom_data* custom = nullptr; + proxyPlugin plugin = {}; + + plugin.name = plugin_name; + plugin.description = plugin_desc; + plugin.PluginUnload = demo_plugin_unload; + plugin.ClientInitConnect = demo_client_init_connect; + plugin.ClientUninitConnect = demo_client_uninit_connect; + plugin.ClientPreConnect = demo_client_pre_connect; + plugin.ClientPostConnect = demo_client_post_connect; + plugin.ClientPostDisconnect = demo_client_post_disconnect; + plugin.ClientX509Certificate = demo_client_x509_certificate; + plugin.ClientLoginFailure = demo_client_login_failure; + plugin.ClientEndPaint = demo_client_end_paint; + plugin.ClientRedirect = demo_client_redirect; + plugin.ServerPostConnect = demo_server_post_connect; + plugin.ServerPeerActivate = demo_server_peer_activate; + plugin.ServerChannelsInit = demo_server_channels_init; + plugin.ServerChannelsFree = demo_server_channels_free; + plugin.ServerSessionEnd = demo_server_session_end; + plugin.KeyboardEvent = demo_filter_keyboard_event; + plugin.UnicodeEvent = demo_filter_unicode_event; + plugin.MouseEvent = demo_mouse_event; + plugin.MouseExEvent = demo_mouse_ex_event; + plugin.ClientChannelData = demo_client_channel_data; + plugin.ServerChannelData = demo_server_channel_data; + plugin.DynamicChannelCreate = demo_dynamic_channel_create; + plugin.ServerFetchTargetAddr = demo_server_fetch_target_addr; + plugin.ServerPeerLogon = demo_server_peer_logon; + + plugin.StaticChannelToIntercept = demo_static_channel_intercept_list; + plugin.DynChannelToIntercept = demo_dyn_channel_intercept_list; + plugin.DynChannelIntercept = demo_dyn_channel_intercept; + + plugin.userdata = userdata; + + custom = new (struct demo_custom_data); + if (!custom) + return FALSE; + + custom->mgr = plugins_manager; + custom->somesetting = 42; + + plugin.custom = custom; + plugin.userdata = userdata; + + return plugins_manager->RegisterPlugin(plugins_manager, &plugin); +} diff --git a/server/proxy/modules/dyn-channel-dump/CMakeLists.txt b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt new file mode 100644 index 0000000..dc0fc53 --- /dev/null +++ b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt @@ -0,0 +1,58 @@ +# +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Proxy Server Demo C++ Module +# +# Copyright 2023 Armin Novak <anovak@thincast.com> +# Copyright 2023 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. +# + +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if (NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(proxy-dyn-channel-dump-plugin + VERSION ${FREERDP_DEFAULT_PROJECT_VERSION} + LANGUAGES CXX +) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/) +include(CommonConfigOptions) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(${PROJECT_NAME} SHARED + dyn-channel-dump.cpp +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + winpr + freerdp + freerdp-client + freerdp-server + freerdp-server-proxy +) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1) + +install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR}) diff --git a/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp new file mode 100644 index 0000000..d80902f --- /dev/null +++ b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp @@ -0,0 +1,436 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server persist-bitmap-filter Module + * + * this module is designed to deactivate all persistent bitmap cache settings a + * client might send. + * + * Copyright 2023 Armin Novak <anovak@thincast.com> + * Copyright 2023 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 <fstream> +#include <iostream> +#include <regex> +#include <vector> +#include <sstream> +#include <string> +#include <algorithm> +#include <map> +#include <memory> +#include <mutex> +#include <atomic> +#if __has_include(<filesystem>) +#include <filesystem> +namespace fs = std::filesystem; +#elif __has_include(<experimental/filesystem>) +#include <experimental/filesystem> +namespace fs = std::experimental::filesystem; +#else +#error Could not find system header "<filesystem>" or "<experimental/filesystem>" +#endif + +#include <freerdp/server/proxy/proxy_modules_api.h> +#include <freerdp/server/proxy/proxy_context.h> + +#include <freerdp/channels/drdynvc.h> +#include <freerdp/channels/rdpgfx.h> +#include <freerdp/utils/gfx.h> + +#define TAG MODULE_TAG("dyn-channel-dump") + +static constexpr char plugin_name[] = "dyn-channel-dump"; +static constexpr char plugin_desc[] = + "This plugin dumps configurable dynamic channel data to a file."; + +static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME }; + +static constexpr char key_path[] = "path"; +static constexpr char key_channels[] = "channels"; + +class PluginData +{ + public: + PluginData(proxyPluginsManager* mgr) : _mgr(mgr), _sessionid(0) + { + } + + proxyPluginsManager* mgr() const + { + return _mgr; + } + + uint64_t session() + { + return _sessionid++; + } + + private: + proxyPluginsManager* _mgr; + uint64_t _sessionid; +}; + +class ChannelData +{ + public: + ChannelData(const std::string& base, const std::vector<std::string>& list, uint64_t sessionid) + : _base(base), _channels_to_dump(list), _session_id(sessionid) + { + char str[64] = {}; + _snprintf(str, sizeof(str), "session-%016" PRIx64, _session_id); + _base /= str; + } + + bool add(const std::string& name, bool back) + { + std::lock_guard<std::mutex> guard(_mux); + if (_map.find(name) == _map.end()) + { + WLog_INFO(TAG, "adding '%s' to dump list", name.c_str()); + _map.insert({ name, 0 }); + } + return true; + } + + std::ofstream stream(const std::string& name, bool back) + { + std::lock_guard<std::mutex> guard(_mux); + auto& atom = _map[name]; + auto count = atom++; + auto path = filepath(name, back, count); + WLog_DBG(TAG, "[%s] writing file '%s'", name.c_str(), path.c_str()); + return std::ofstream(path); + } + + bool dump_enabled(const std::string& name) const + { + if (name.empty()) + { + WLog_WARN(TAG, "empty dynamic channel name, skipping"); + return false; + } + + auto enabled = std::find(_channels_to_dump.begin(), _channels_to_dump.end(), name) != + _channels_to_dump.end(); + WLog_DBG(TAG, "channel '%s' dumping %s", name.c_str(), enabled ? "enabled" : "disabled"); + return enabled; + } + + bool ensure_path_exists() + { + if (!fs::exists(_base)) + { + if (!fs::create_directories(_base)) + { + WLog_ERR(TAG, "Failed to create dump directory %s", _base.c_str()); + return false; + } + } + else if (!fs::is_directory(_base)) + { + WLog_ERR(TAG, "dump path %s is not a directory", _base.c_str()); + return false; + } + return true; + } + + bool create() + { + if (!ensure_path_exists()) + return false; + + if (_channels_to_dump.empty()) + { + WLog_ERR(TAG, "Empty configuration entry [%s/%s], can not continue", plugin_name, + key_channels); + return false; + } + return true; + } + + uint64_t session() const + { + return _session_id; + } + + private: + fs::path filepath(const std::string& channel, bool back, uint64_t count) const + { + auto name = idstr(channel, back); + char cstr[32] = {}; + _snprintf(cstr, sizeof(cstr), "%016" PRIx64 "-", count); + auto path = _base / cstr; + path += name; + path += ".dump"; + return path; + } + + std::string idstr(const std::string& name, bool back) const + { + std::stringstream ss; + ss << name << "."; + if (back) + ss << "back"; + else + ss << "front"; + return ss.str(); + } + + private: + fs::path _base; + std::vector<std::string> _channels_to_dump; + + std::mutex _mux; + std::map<std::string, uint64_t> _map; + uint64_t _session_id; +}; + +static PluginData* dump_get_plugin_data(proxyPlugin* plugin) +{ + WINPR_ASSERT(plugin); + + auto plugindata = static_cast<PluginData*>(plugin->custom); + WINPR_ASSERT(plugindata); + return plugindata; +} + +static ChannelData* dump_get_plugin_data(proxyPlugin* plugin, proxyData* pdata) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto plugindata = dump_get_plugin_data(plugin); + WINPR_ASSERT(plugindata); + + auto mgr = plugindata->mgr(); + WINPR_ASSERT(mgr); + + WINPR_ASSERT(mgr->GetPluginData); + return static_cast<ChannelData*>(mgr->GetPluginData(mgr, plugin_name, pdata)); +} + +static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, ChannelData* data) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto plugindata = dump_get_plugin_data(plugin); + WINPR_ASSERT(plugindata); + + auto mgr = plugindata->mgr(); + WINPR_ASSERT(mgr); + + auto cdata = dump_get_plugin_data(plugin, pdata); + delete cdata; + + WINPR_ASSERT(mgr->SetPluginData); + return mgr->SetPluginData(mgr, plugin_name, pdata, data); +} + +static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata, const std::string& name) +{ + auto config = dump_get_plugin_data(plugin, pdata); + if (!config) + { + WLog_ERR(TAG, "Missing channel data"); + return false; + } + return config->dump_enabled(name); +} + +static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + data->intercept = dump_channel_enabled(plugin, pdata, data->name); + if (data->intercept) + { + auto cdata = dump_get_plugin_data(plugin, pdata); + if (!cdata) + return FALSE; + + if (!cdata->add(data->name, false)) + { + WLog_ERR(TAG, "failed to create files for '%s'", data->name); + } + if (!cdata->add(data->name, true)) + { + WLog_ERR(TAG, "failed to create files for '%s'", data->name); + } + WLog_INFO(TAG, "Dumping channel '%s'", data->name); + } + return TRUE; +} + +static BOOL dump_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyChannelToInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(), + data->name) != plugin_static_intercept.end(); + if (intercept) + { + WLog_INFO(TAG, "intercepting channel '%s'", data->name); + data->intercept = TRUE; + } + + return TRUE; +} + +static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg) +{ + auto data = static_cast<proxyDynChannelInterceptData*>(arg); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(data); + + data->result = PF_CHANNEL_RESULT_PASS; + if (dump_channel_enabled(plugin, pdata, data->name)) + { + WLog_DBG(TAG, "intercepting channel '%s'", data->name); + auto cdata = dump_get_plugin_data(plugin, pdata); + if (!cdata) + { + WLog_ERR(TAG, "Missing channel data"); + return FALSE; + } + + if (!cdata->ensure_path_exists()) + return FALSE; + + auto stream = cdata->stream(data->name, data->isBackData); + auto buffer = reinterpret_cast<const char*>(Stream_ConstBuffer(data->data)); + if (!stream.is_open() || !stream.good()) + { + WLog_ERR(TAG, "Could not write to stream"); + return FALSE; + } + stream.write(buffer, Stream_Length(data->data)); + if (stream.fail()) + { + WLog_ERR(TAG, "Could not write to stream"); + return FALSE; + } + stream.flush(); + } + + return TRUE; +} + +static std::vector<std::string> split(const std::string& input, const std::string& regex) +{ + // passing -1 as the submatch index parameter performs splitting + std::regex re(regex); + std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 }; + std::sregex_token_iterator last; + return { first, last }; +} + +static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata, void*) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto custom = dump_get_plugin_data(plugin); + WINPR_ASSERT(custom); + + auto config = pdata->config; + WINPR_ASSERT(config); + + auto cpath = pf_config_get(config, plugin_name, key_path); + if (!cpath) + { + WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name, + key_path); + return FALSE; + } + auto cchannels = pf_config_get(config, plugin_name, key_channels); + if (!cchannels) + { + WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name, + key_channels); + return FALSE; + } + + std::string path(cpath); + std::string channels(cchannels); + std::vector<std::string> list = split(channels, "[;,]"); + auto cfg = new ChannelData(path, list, custom->session()); + if (!cfg || !cfg->create()) + { + delete cfg; + return FALSE; + } + + dump_set_plugin_data(plugin, pdata, cfg); + + WLog_DBG(TAG, "starting session dump %" PRIu64, cfg->session()); + return TRUE; +} + +static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata, void*) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + + auto cfg = dump_get_plugin_data(plugin, pdata); + if (cfg) + WLog_DBG(TAG, "ending session dump %" PRIu64, cfg->session()); + dump_set_plugin_data(plugin, pdata, nullptr); + return TRUE; +} + +static BOOL dump_unload(proxyPlugin* plugin) +{ + if (!plugin) + return TRUE; + delete static_cast<PluginData*>(plugin->custom); + return TRUE; +} + +extern "C" FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, + void* userdata); + +BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata) +{ + proxyPlugin plugin = {}; + + plugin.name = plugin_name; + plugin.description = plugin_desc; + + plugin.PluginUnload = dump_unload; + plugin.ServerSessionStarted = dump_session_started; + plugin.ServerSessionEnd = dump_session_end; + + plugin.StaticChannelToIntercept = dump_static_channel_intercept_list; + plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list; + plugin.DynChannelIntercept = dump_dyn_channel_intercept; + + plugin.custom = new PluginData(plugins_manager); + if (!plugin.custom) + return FALSE; + plugin.userdata = userdata; + + return plugins_manager->RegisterPlugin(plugins_manager, &plugin); +} diff --git a/server/proxy/pf_channel.c b/server/proxy/pf_channel.c new file mode 100644 index 0000000..a8f66c4 --- /dev/null +++ b/server/proxy/pf_channel.c @@ -0,0 +1,355 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2022 David Fort <contact@hardening-consulting.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/freerdp.h> +#include <freerdp/server/proxy/proxy_log.h> + +#include "proxy_modules.h" +#include "pf_channel.h" + +#define TAG PROXY_TAG("channel") + +/** @brief a tracker for channel packets */ +struct _ChannelStateTracker +{ + pServerStaticChannelContext* channel; + ChannelTrackerMode mode; + wStream* currentPacket; + size_t currentPacketReceived; + size_t currentPacketSize; + size_t currentPacketFragments; + + ChannelTrackerPeekFn peekFn; + void* trackerData; + proxyData* pdata; +}; + +static BOOL channelTracker_resetCurrentPacket(ChannelStateTracker* tracker) +{ + WINPR_ASSERT(tracker); + + BOOL create = TRUE; + if (tracker->currentPacket) + { + const size_t cap = Stream_Capacity(tracker->currentPacket); + if (cap < 1 * 1000 * 1000) + create = FALSE; + else + Stream_Free(tracker->currentPacket, TRUE); + } + + if (create) + tracker->currentPacket = Stream_New(NULL, 10 * 1024); + if (!tracker->currentPacket) + return FALSE; + Stream_SetPosition(tracker->currentPacket, 0); + return TRUE; +} + +ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel, + ChannelTrackerPeekFn fn, void* data) +{ + ChannelStateTracker* ret = calloc(1, sizeof(ChannelStateTracker)); + if (!ret) + return ret; + + WINPR_ASSERT(fn); + + ret->channel = channel; + ret->peekFn = fn; + + if (!channelTracker_setCustomData(ret, data)) + goto fail; + + if (!channelTracker_resetCurrentPacket(ret)) + goto fail; + + return ret; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + channelTracker_free(ret); + WINPR_PRAGMA_DIAG_POP + return NULL; +} + +PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize, + UINT32 flags, size_t totalSize) +{ + PfChannelResult result = PF_CHANNEL_RESULT_ERROR; + BOOL firstPacket = (flags & CHANNEL_FLAG_FIRST); + BOOL lastPacket = (flags & CHANNEL_FLAG_LAST); + + WINPR_ASSERT(tracker); + + WLog_VRB(TAG, "channelTracker_update(%s): sz=%" PRIuz " first=%d last=%d", + tracker->channel->channel_name, xsize, firstPacket, lastPacket); + if (flags & CHANNEL_FLAG_FIRST) + { + if (!channelTracker_resetCurrentPacket(tracker)) + return FALSE; + channelTracker_setCurrentPacketSize(tracker, totalSize); + tracker->currentPacketReceived = 0; + tracker->currentPacketFragments = 0; + } + + { + const size_t currentPacketSize = channelTracker_getCurrentPacketSize(tracker); + if (tracker->currentPacketReceived + xsize > currentPacketSize) + WLog_INFO(TAG, "cumulated size is bigger (%" PRIuz ") than total size (%" PRIuz ")", + tracker->currentPacketReceived + xsize, currentPacketSize); + } + + tracker->currentPacketReceived += xsize; + tracker->currentPacketFragments++; + + switch (channelTracker_getMode(tracker)) + { + case CHANNEL_TRACKER_PEEK: + { + wStream* currentPacket = channelTracker_getCurrentPacket(tracker); + if (!Stream_EnsureRemainingCapacity(currentPacket, xsize)) + return PF_CHANNEL_RESULT_ERROR; + + Stream_Write(currentPacket, xdata, xsize); + + WINPR_ASSERT(tracker->peekFn); + result = tracker->peekFn(tracker, firstPacket, lastPacket); + } + break; + case CHANNEL_TRACKER_PASS: + result = PF_CHANNEL_RESULT_PASS; + break; + case CHANNEL_TRACKER_DROP: + result = PF_CHANNEL_RESULT_DROP; + break; + } + + if (lastPacket) + { + const size_t currentPacketSize = channelTracker_getCurrentPacketSize(tracker); + channelTracker_setMode(tracker, CHANNEL_TRACKER_PEEK); + + if (tracker->currentPacketReceived != currentPacketSize) + WLog_INFO(TAG, "cumulated size(%" PRIuz ") does not match total size (%" PRIuz ")", + tracker->currentPacketReceived, currentPacketSize); + } + + return result; +} + +void channelTracker_free(ChannelStateTracker* t) +{ + if (!t) + return; + + Stream_Free(t->currentPacket, TRUE); + free(t); +} + +/** + * Flushes the current accumulated tracker content, if it's the first packet, then + * when can just return that the packet shall be passed, otherwise to have to refragment + * the accumulated current packet. + */ + +PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last, + BOOL toBack) +{ + proxyData* pdata = NULL; + pServerContext* ps = NULL; + pServerStaticChannelContext* channel = NULL; + UINT32 flags = CHANNEL_FLAG_FIRST; + BOOL r = 0; + const char* direction = toBack ? "F->B" : "B->F"; + const size_t currentPacketSize = channelTracker_getCurrentPacketSize(t); + wStream* currentPacket = channelTracker_getCurrentPacket(t); + + WINPR_ASSERT(t); + + WLog_VRB(TAG, "channelTracker_flushCurrent(%s): %s sz=%" PRIuz " first=%d last=%d", + t->channel->channel_name, direction, Stream_GetPosition(currentPacket), first, last); + + if (first) + return PF_CHANNEL_RESULT_PASS; + + pdata = t->pdata; + channel = t->channel; + if (last) + flags |= CHANNEL_FLAG_LAST; + + if (toBack) + { + proxyChannelDataEventInfo ev; + + ev.channel_id = channel->front_channel_id; + ev.channel_name = channel->channel_name; + ev.data = Stream_Buffer(currentPacket); + ev.data_len = Stream_GetPosition(currentPacket); + ev.flags = flags; + ev.total_size = currentPacketSize; + + if (!pdata->pc->sendChannelData) + return PF_CHANNEL_RESULT_ERROR; + + return pdata->pc->sendChannelData(pdata->pc, &ev) ? PF_CHANNEL_RESULT_DROP + : PF_CHANNEL_RESULT_ERROR; + } + + ps = pdata->ps; + r = ps->context.peer->SendChannelPacket(ps->context.peer, channel->front_channel_id, + currentPacketSize, flags, Stream_Buffer(currentPacket), + Stream_GetPosition(currentPacket)); + + return r ? PF_CHANNEL_RESULT_DROP : PF_CHANNEL_RESULT_ERROR; +} + +static PfChannelResult pf_channel_generic_back_data(proxyData* pdata, + const pServerStaticChannelContext* channel, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + proxyChannelDataEventInfo ev = { 0 }; + + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + switch (channel->channelMode) + { + case PF_UTILS_CHANNEL_PASSTHROUGH: + ev.channel_id = channel->back_channel_id; + ev.channel_name = channel->channel_name; + ev.data = xdata; + ev.data_len = xsize; + ev.flags = flags; + ev.total_size = totalSize; + + if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, + pdata, &ev)) + return PF_CHANNEL_RESULT_DROP; /* Silently drop */ + + return PF_CHANNEL_RESULT_PASS; + + case PF_UTILS_CHANNEL_INTERCEPT: + /* TODO */ + case PF_UTILS_CHANNEL_BLOCK: + default: + return PF_CHANNEL_RESULT_DROP; + } +} + +static PfChannelResult pf_channel_generic_front_data(proxyData* pdata, + const pServerStaticChannelContext* channel, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + proxyChannelDataEventInfo ev = { 0 }; + + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + switch (channel->channelMode) + { + case PF_UTILS_CHANNEL_PASSTHROUGH: + ev.channel_id = channel->front_channel_id; + ev.channel_name = channel->channel_name; + ev.data = xdata; + ev.data_len = xsize; + ev.flags = flags; + ev.total_size = totalSize; + + if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, + pdata, &ev)) + return PF_CHANNEL_RESULT_DROP; /* Silently drop */ + + return PF_CHANNEL_RESULT_PASS; + + case PF_UTILS_CHANNEL_INTERCEPT: + /* TODO */ + case PF_UTILS_CHANNEL_BLOCK: + default: + return PF_CHANNEL_RESULT_DROP; + } +} + +BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel) +{ + channel->onBackData = pf_channel_generic_back_data; + channel->onFrontData = pf_channel_generic_front_data; + return TRUE; +} + +BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode) +{ + WINPR_ASSERT(tracker); + tracker->mode = mode; + return TRUE; +} + +ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker) +{ + WINPR_ASSERT(tracker); + return tracker->mode; +} + +BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata) +{ + WINPR_ASSERT(tracker); + tracker->pdata = pdata; + return TRUE; +} + +proxyData* channelTracker_getPData(ChannelStateTracker* tracker) +{ + WINPR_ASSERT(tracker); + return tracker->pdata; +} + +wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker) +{ + WINPR_ASSERT(tracker); + return tracker->currentPacket; +} + +BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data) +{ + WINPR_ASSERT(tracker); + tracker->trackerData = data; + return TRUE; +} + +void* channelTracker_getCustomData(ChannelStateTracker* tracker) +{ + WINPR_ASSERT(tracker); + return tracker->trackerData; +} + +size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker) +{ + WINPR_ASSERT(tracker); + return tracker->currentPacketSize; +} + +BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size) +{ + WINPR_ASSERT(tracker); + tracker->currentPacketSize = size; + return TRUE; +} diff --git a/server/proxy/pf_channel.h b/server/proxy/pf_channel.h new file mode 100644 index 0000000..9e0da7e --- /dev/null +++ b/server/proxy/pf_channel.h @@ -0,0 +1,64 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * + * Copyright 2022 David Fort <contact@hardening-consulting.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. + */ +#ifndef SERVER_PROXY_PF_CHANNEL_H_ +#define SERVER_PROXY_PF_CHANNEL_H_ + +#include <freerdp/server/proxy/proxy_context.h> + +/** @brief operating mode of a channel tracker */ +typedef enum +{ + CHANNEL_TRACKER_PEEK, /*!< inquiring content, accumulating packet fragments */ + CHANNEL_TRACKER_PASS, /*!< pass all the fragments of the current packet */ + CHANNEL_TRACKER_DROP /*!< drop all the fragments of the current packet */ +} ChannelTrackerMode; + +typedef struct _ChannelStateTracker ChannelStateTracker; +typedef PfChannelResult (*ChannelTrackerPeekFn)(ChannelStateTracker* tracker, BOOL first, + BOOL lastPacket); + +void channelTracker_free(ChannelStateTracker* t); + +WINPR_ATTR_MALLOC(channelTracker_free, 1) +ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel, + ChannelTrackerPeekFn fn, void* data); + +BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode); +ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker); + +BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata); +proxyData* channelTracker_getPData(ChannelStateTracker* tracker); + +BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data); +void* channelTracker_getCustomData(ChannelStateTracker* tracker); + +wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker); + +size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker); +BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size); + +PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize, + UINT32 flags, size_t totalSize); + +PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last, + BOOL toFront); + +BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel); + +#endif /* SERVER_PROXY_PF_CHANNEL_H_ */ diff --git a/server/proxy/pf_client.c b/server/proxy/pf_client.c new file mode 100644 index 0000000..2f34f97 --- /dev/null +++ b/server/proxy/pf_client.c @@ -0,0 +1,1102 @@ +/** + * 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 <freerdp/freerdp.h> +#include <freerdp/gdi/gdi.h> +#include <freerdp/client/cmdline.h> + +#include <freerdp/server/proxy/proxy_log.h> +#include <freerdp/channels/drdynvc.h> +#include <freerdp/channels/encomsp.h> +#include <freerdp/channels/rdpdr.h> +#include <freerdp/channels/rdpsnd.h> +#include <freerdp/channels/cliprdr.h> +#include <freerdp/channels/channels.h> + +#include "pf_client.h" +#include "pf_channel.h" +#include <freerdp/server/proxy/proxy_context.h> +#include "pf_update.h" +#include "pf_input.h" +#include <freerdp/server/proxy/proxy_config.h> +#include "proxy_modules.h" +#include "pf_utils.h" +#include "channels/pf_channel_rdpdr.h" +#include "channels/pf_channel_smartcard.h" + +#define TAG PROXY_TAG("client") + +static void channel_data_free(void* obj); +static BOOL proxy_server_reactivate(rdpContext* ps, const rdpContext* pc) +{ + WINPR_ASSERT(ps); + WINPR_ASSERT(pc); + + if (!pf_context_copy_settings(ps->settings, pc->settings)) + return FALSE; + + /* + * DesktopResize causes internal function rdp_server_reactivate to be called, + * which causes the reactivation. + */ + WINPR_ASSERT(ps->update); + if (!ps->update->DesktopResize(ps)) + return FALSE; + + return TRUE; +} + +static void pf_client_on_error_info(void* ctx, const ErrorInfoEventArgs* e) +{ + pClientContext* pc = (pClientContext*)ctx; + pServerContext* ps = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + WINPR_ASSERT(e); + ps = pc->pdata->ps; + WINPR_ASSERT(ps); + + if (e->code == ERRINFO_NONE) + return; + + PROXY_LOG_WARN(TAG, pc, "received ErrorInfo PDU. code=0x%08" PRIu32 ", message: %s", e->code, + freerdp_get_error_info_string(e->code)); + + /* forward error back to client */ + freerdp_set_error_info(ps->context.rdp, e->code); + freerdp_send_error_info(ps->context.rdp); +} + +static void pf_client_on_activated(void* ctx, const ActivatedEventArgs* e) +{ + pClientContext* pc = (pClientContext*)ctx; + pServerContext* ps = NULL; + freerdp_peer* peer = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + WINPR_ASSERT(e); + + ps = pc->pdata->ps; + WINPR_ASSERT(ps); + peer = ps->context.peer; + WINPR_ASSERT(peer); + WINPR_ASSERT(peer->context); + + PROXY_LOG_INFO(TAG, pc, "client activated, registering server input callbacks"); + + /* Register server input/update callbacks only after proxy client is fully activated */ + pf_server_register_input_callbacks(peer->context->input); + pf_server_register_update_callbacks(peer->context->update); +} + +static BOOL pf_client_load_rdpsnd(pClientContext* pc) +{ + rdpContext* context = (rdpContext*)pc; + pServerContext* ps = NULL; + const proxyConfig* config = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + ps = pc->pdata->ps; + WINPR_ASSERT(ps); + config = pc->pdata->config; + WINPR_ASSERT(config); + + /* + * if AudioOutput is enabled in proxy and client connected with rdpsnd, use proxy as rdpsnd + * backend. Otherwise, use sys:fake. + */ + if (!freerdp_static_channel_collection_find(context->settings, RDPSND_CHANNEL_NAME)) + { + const char* params[2] = { RDPSND_CHANNEL_NAME, "sys:fake" }; + + if (!freerdp_client_add_static_channel(context->settings, ARRAYSIZE(params), params)) + return FALSE; + } + + return TRUE; +} + +static BOOL pf_client_use_peer_load_balance_info(pClientContext* pc) +{ + pServerContext* ps = NULL; + rdpSettings* settings = NULL; + DWORD lb_info_len = 0; + const char* lb_info = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + ps = pc->pdata->ps; + WINPR_ASSERT(ps); + settings = pc->context.settings; + WINPR_ASSERT(settings); + + lb_info = freerdp_nego_get_routing_token(&ps->context, &lb_info_len); + if (!lb_info) + return TRUE; + + return freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, lb_info, + lb_info_len); +} + +static BOOL str_is_empty(const char* str) +{ + if (!str) + return TRUE; + if (strlen(str) == 0) + return TRUE; + return FALSE; +} + +static BOOL pf_client_use_proxy_smartcard_auth(const rdpSettings* settings) +{ + BOOL enable = freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon); + const char* key = freerdp_settings_get_string(settings, FreeRDP_SmartcardPrivateKey); + const char* cert = freerdp_settings_get_string(settings, FreeRDP_SmartcardCertificate); + + if (!enable) + return FALSE; + + if (str_is_empty(key)) + return FALSE; + + if (str_is_empty(cert)) + return FALSE; + + return TRUE; +} + +static BOOL freerdp_client_load_static_channel_addin(rdpChannels* channels, rdpSettings* settings, + const char* name, void* data) +{ + PVIRTUALCHANNELENTRY entry = NULL; + PVIRTUALCHANNELENTRYEX entryEx = NULL; + entryEx = (PVIRTUALCHANNELENTRYEX)(void*)freerdp_load_channel_addin_entry( + name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX); + + if (!entryEx) + entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); + + if (entryEx) + { + if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0) + { + WLog_INFO(TAG, "loading channelEx %s", name); + return TRUE; + } + } + else if (entry) + { + if (freerdp_channels_client_load(channels, settings, entry, data) == 0) + { + WLog_INFO(TAG, "loading channel %s", name); + return TRUE; + } + } + + return FALSE; +} + +static BOOL pf_client_pre_connect(freerdp* instance) +{ + pClientContext* pc = NULL; + pServerContext* ps = NULL; + const proxyConfig* config = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(instance); + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + ps = pc->pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + config = ps->pdata->config; + WINPR_ASSERT(config); + settings = instance->context->settings; + WINPR_ASSERT(settings); + + /* + * as the client's settings are copied from the server's, GlyphSupportLevel might not be + * GLYPH_SUPPORT_NONE. the proxy currently do not support GDI & GLYPH_SUPPORT_CACHE, so + * GlyphCacheSupport must be explicitly set to GLYPH_SUPPORT_NONE. + * + * Also, OrderSupport need to be zeroed, because it is currently not supported. + */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GLYPH_SUPPORT_NONE)) + return FALSE; + + void* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport); + ZeroMemory(OrderSupport, 32); + + if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, DRDYNVC_SVC_CHANNEL_NAME)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE)) + return FALSE; + } + + /* Multimon */ + if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE)) + return FALSE; + + /* Sound */ + if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, config->AudioInput) || + !freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, config->AudioOutput) || + !freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, + config->DeviceRedirection) || + !freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, + config->DisplayControl) || + !freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, config->Multitouch)) + return FALSE; + + if (config->RemoteApp) + { + if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RAIL_SVC_CHANNEL_NAME)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteApplicationMode, TRUE)) + return FALSE; + } + } + + if (config->DeviceRedirection) + { + if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RDPDR_SVC_CHANNEL_NAME)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + return FALSE; + } + } + + /* Display control */ + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, config->DisplayControl)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, + config->DisplayControl)) + return FALSE; + + if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, ENCOMSP_SVC_CHANNEL_NAME)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE)) + return FALSE; + } + + if (config->Clipboard) + { + if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, CLIPRDR_SVC_CHANNEL_NAME)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, config->Clipboard)) + return FALSE; + } + } + + if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, TRUE)) + return FALSE; + + PubSub_SubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info); + PubSub_SubscribeActivated(instance->context->pubSub, pf_client_on_activated); + if (!pf_client_use_peer_load_balance_info(pc)) + return FALSE; + + return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_PRE_CONNECT, pc->pdata, pc); +} + +/** @brief arguments for updateBackIdFn */ +typedef struct +{ + pServerContext* ps; + const char* name; + UINT32 backId; +} UpdateBackIdArgs; + +static BOOL updateBackIdFn(const void* key, void* value, void* arg) +{ + pServerStaticChannelContext* current = (pServerStaticChannelContext*)value; + UpdateBackIdArgs* updateArgs = (UpdateBackIdArgs*)arg; + + if (strcmp(updateArgs->name, current->channel_name) != 0) + return TRUE; + + current->back_channel_id = updateArgs->backId; + if (!HashTable_Insert(updateArgs->ps->channelsByBackId, ¤t->back_channel_id, current)) + { + WLog_ERR(TAG, "error inserting channel in channelsByBackId table"); + } + return FALSE; +} + +static BOOL pf_client_update_back_id(pServerContext* ps, const char* name, UINT32 backId) +{ + UpdateBackIdArgs res = { ps, name, backId }; + + return HashTable_Foreach(ps->channelsByFrontId, updateBackIdFn, &res) == FALSE; +} + +static BOOL pf_client_load_channels(freerdp* instance) +{ + pClientContext* pc = NULL; + pServerContext* ps = NULL; + const proxyConfig* config = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(instance); + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + ps = pc->pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + config = ps->pdata->config; + WINPR_ASSERT(config); + settings = instance->context->settings; + WINPR_ASSERT(settings); + /** + * Load all required plugins / channels / libraries specified by current + * settings. + */ + PROXY_LOG_INFO(TAG, pc, "Loading addins"); + + if (!pf_client_load_rdpsnd(pc)) + { + PROXY_LOG_ERR(TAG, pc, "Failed to load rdpsnd client"); + return FALSE; + } + + if (!pf_utils_is_passthrough(config)) + { + if (!freerdp_client_load_addins(instance->context->channels, settings)) + { + PROXY_LOG_ERR(TAG, pc, "Failed to load addins"); + return FALSE; + } + } + else + { + if (!pf_channel_rdpdr_client_new(pc)) + return FALSE; +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (!pf_channel_smartcard_client_new(pc)) + return FALSE; +#endif + /* Copy the current channel settings from the peer connection to the client. */ + if (!freerdp_channels_from_mcs(settings, &ps->context)) + return FALSE; + + /* Filter out channels we do not want */ + { + CHANNEL_DEF* channels = (CHANNEL_DEF*)freerdp_settings_get_pointer_array_writable( + settings, FreeRDP_ChannelDefArray, 0); + size_t size = freerdp_settings_get_uint32(settings, FreeRDP_ChannelCount); + UINT32 id = MCS_GLOBAL_CHANNEL_ID + 1; + + WINPR_ASSERT(channels || (size == 0)); + + size_t x = 0; + for (; x < size;) + { + CHANNEL_DEF* cur = &channels[x]; + proxyChannelDataEventInfo dev = { 0 }; + + dev.channel_name = cur->name; + dev.flags = cur->options; + + /* Filter out channels blocked by config */ + if (!pf_modules_run_filter(pc->pdata->module, + FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, pc->pdata, + &dev)) + { + const size_t s = size - MIN(size, x + 1); + memmove(cur, &cur[1], sizeof(CHANNEL_DEF) * s); + size--; + } + else + { + if (!pf_client_update_back_id(ps, cur->name, id++)) + { + WLog_ERR(TAG, "unable to update backid for channel %s", cur->name); + return FALSE; + } + x++; + } + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, x)) + return FALSE; + } + } + return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_LOAD_CHANNELS, pc->pdata, pc); +} + +static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + pClientContext* pc = NULL; + pServerContext* ps = NULL; + proxyData* pdata = NULL; + pServerStaticChannelContext* channel = NULL; + UINT64 channelId64 = channelId; + + WINPR_ASSERT(instance); + WINPR_ASSERT(xdata || (xsize == 0)); + + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + + ps = pc->pdata->ps; + WINPR_ASSERT(ps); + + pdata = ps->pdata; + WINPR_ASSERT(pdata); + + channel = HashTable_GetItemValue(ps->channelsByBackId, &channelId64); + if (!channel) + return TRUE; + + WINPR_ASSERT(channel->onBackData); + switch (channel->onBackData(pdata, channel, xdata, xsize, flags, totalSize)) + { + case PF_CHANNEL_RESULT_PASS: + /* Ignore messages for channels that can not be mapped. + * The client might not have enabled support for this specific channel, + * so just drop the message. */ + if (channel->front_channel_id == 0) + return TRUE; + + return ps->context.peer->SendChannelPacket(ps->context.peer, channel->front_channel_id, + totalSize, flags, xdata, xsize); + case PF_CHANNEL_RESULT_DROP: + return TRUE; + case PF_CHANNEL_RESULT_ERROR: + default: + return FALSE; + } +} + +static BOOL pf_client_on_server_heartbeat(freerdp* instance, BYTE period, BYTE count1, BYTE count2) +{ + pClientContext* pc = NULL; + pServerContext* ps = NULL; + + WINPR_ASSERT(instance); + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + ps = pc->pdata->ps; + WINPR_ASSERT(ps); + + return freerdp_heartbeat_send_heartbeat_pdu(ps->context.peer, period, count1, count2); +} + +static BOOL pf_client_send_channel_data(pClientContext* pc, const proxyChannelDataEventInfo* ev) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(ev); + + return Queue_Enqueue(pc->cached_server_channel_data, ev); +} + +static BOOL sendQueuedChannelData(pClientContext* pc) +{ + BOOL rc = TRUE; + + WINPR_ASSERT(pc); + + if (pc->connected) + { + proxyChannelDataEventInfo* ev = NULL; + + Queue_Lock(pc->cached_server_channel_data); + while (rc && (ev = Queue_Dequeue(pc->cached_server_channel_data))) + { + UINT16 channelId = 0; + WINPR_ASSERT(pc->context.instance); + + channelId = freerdp_channels_get_id_by_name(pc->context.instance, ev->channel_name); + /* Ignore unmappable channels */ + if ((channelId == 0) || (channelId == UINT16_MAX)) + rc = TRUE; + else + { + WINPR_ASSERT(pc->context.instance->SendChannelPacket); + rc = pc->context.instance->SendChannelPacket(pc->context.instance, channelId, + ev->total_size, ev->flags, ev->data, + ev->data_len); + } + channel_data_free(ev); + } + + Queue_Unlock(pc->cached_server_channel_data); + } + + return rc; +} + +/** + * Called after a RDP connection was successfully established. + * Settings might have changed during negotiation of client / server feature + * support. + * + * Set up local framebuffers and painting callbacks. + * If required, register pointer callbacks to change the local mouse cursor + * when hovering over the RDP window + */ +static BOOL pf_client_post_connect(freerdp* instance) +{ + rdpContext* context = NULL; + rdpSettings* settings = NULL; + rdpUpdate* update = NULL; + rdpContext* ps = NULL; + pClientContext* pc = NULL; + const proxyConfig* config = NULL; + + WINPR_ASSERT(instance); + context = instance->context; + WINPR_ASSERT(context); + settings = context->settings; + WINPR_ASSERT(settings); + update = context->update; + WINPR_ASSERT(update); + pc = (pClientContext*)context; + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + ps = (rdpContext*)pc->pdata->ps; + WINPR_ASSERT(ps); + config = pc->pdata->config; + WINPR_ASSERT(config); + + if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_CONNECT, pc->pdata, pc)) + return FALSE; + + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) + return FALSE; + + WINPR_ASSERT(freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi)); + + pf_client_register_update_callbacks(update); + + /* virtual channels receive data hook */ + pc->client_receive_channel_data_original = instance->ReceiveChannelData; + instance->ReceiveChannelData = pf_client_receive_channel_data_hook; + + instance->heartbeat->ServerHeartbeat = pf_client_on_server_heartbeat; + + pc->connected = TRUE; + + /* Send cached channel data */ + sendQueuedChannelData(pc); + + /* + * after the connection fully established and settings were negotiated with target server, + * send a reactivation sequence to the client with the negotiated settings. This way, + * settings are synchorinized between proxy's peer and and remote target. + */ + return proxy_server_reactivate(ps, context); +} + +/* This function is called whether a session ends by failure or success. + * Clean up everything allocated by pre_connect and post_connect. + */ +static void pf_client_post_disconnect(freerdp* instance) +{ + pClientContext* pc = NULL; + proxyData* pdata = NULL; + + if (!instance) + return; + + if (!instance->context) + return; + + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + pf_channel_smartcard_client_free(pc); +#endif + + pf_channel_rdpdr_client_free(pc); + + pc->connected = FALSE; + pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_DISCONNECT, pc->pdata, pc); + + PubSub_UnsubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info); + gdi_free(instance); + + /* Only close the connection if NLA fallback process is done */ + if (!pc->allow_next_conn_failure) + proxy_data_abort_connect(pdata); +} + +static BOOL pf_client_redirect(freerdp* instance) +{ + pClientContext* pc = NULL; + proxyData* pdata = NULL; + + if (!instance) + return FALSE; + + if (!instance->context) + return FALSE; + + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + pf_channel_smartcard_client_reset(pc); +#endif + pf_channel_rdpdr_client_reset(pc); + + return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_REDIRECT, pc->pdata, pc); +} + +/* + * pf_client_should_retry_without_nla: + * + * returns TRUE if in case of connection failure, the client should try again without NLA. + * Otherwise, returns FALSE. + */ +static BOOL pf_client_should_retry_without_nla(pClientContext* pc) +{ + rdpSettings* settings = NULL; + const proxyConfig* config = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + settings = pc->context.settings; + WINPR_ASSERT(settings); + config = pc->pdata->config; + WINPR_ASSERT(config); + + if (!config->ClientAllowFallbackToTls || + !freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity)) + return FALSE; + + return config->ClientTlsSecurity || config->ClientRdpSecurity; +} + +static void pf_client_set_security_settings(pClientContext* pc) +{ + rdpSettings* settings = NULL; + const proxyConfig* config = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + settings = pc->context.settings; + WINPR_ASSERT(settings); + config = pc->pdata->config; + WINPR_ASSERT(config); + + freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, config->ClientRdpSecurity); + freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, config->ClientTlsSecurity); + freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, config->ClientNlaSecurity); + + if (pf_client_use_proxy_smartcard_auth(settings)) + { + /* Smartcard authentication requires smartcard redirection to be enabled */ + freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE); + + /* Reset username/domain, we will get that info later from the sc cert */ + freerdp_settings_set_string(settings, FreeRDP_Username, NULL); + freerdp_settings_set_string(settings, FreeRDP_Domain, NULL); + } +} + +static BOOL pf_client_connect_without_nla(pClientContext* pc) +{ + freerdp* instance = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(pc); + instance = pc->context.instance; + WINPR_ASSERT(instance); + + if (!freerdp_context_reset(instance)) + return FALSE; + + settings = pc->context.settings; + WINPR_ASSERT(settings); + + /* If already disabled abort early. */ + if (!freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity)) + return FALSE; + + /* disable NLA */ + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + return FALSE; + + /* do not allow next connection failure */ + pc->allow_next_conn_failure = FALSE; + return freerdp_connect(instance); +} + +static BOOL pf_client_connect(freerdp* instance) +{ + pClientContext* pc = NULL; + rdpSettings* settings = NULL; + BOOL rc = FALSE; + BOOL retry = FALSE; + + WINPR_ASSERT(instance); + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + settings = instance->context->settings; + WINPR_ASSERT(settings); + + PROXY_LOG_INFO(TAG, pc, "connecting using client info: Username: %s, Domain: %s", + freerdp_settings_get_string(settings, FreeRDP_Username), + freerdp_settings_get_string(settings, FreeRDP_Domain)); + + pf_client_set_security_settings(pc); + if (pf_client_should_retry_without_nla(pc)) + retry = pc->allow_next_conn_failure = TRUE; + + PROXY_LOG_INFO(TAG, pc, "connecting using security settings: rdp=%d, tls=%d, nla=%d", + freerdp_settings_get_bool(settings, FreeRDP_RdpSecurity), + freerdp_settings_get_bool(settings, FreeRDP_TlsSecurity), + freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity)); + + if (!freerdp_connect(instance)) + { + if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_LOGIN_FAILURE, pc->pdata, pc)) + goto out; + + if (!retry) + goto out; + + PROXY_LOG_ERR(TAG, pc, "failed to connect with NLA. retrying to connect without NLA"); + if (!pf_client_connect_without_nla(pc)) + { + PROXY_LOG_ERR(TAG, pc, "pf_client_connect_without_nla failed!"); + goto out; + } + } + + rc = TRUE; +out: + pc->allow_next_conn_failure = FALSE; + return rc; +} + +/** + * RDP main loop. + * Connects RDP, loops while running and handles event and dispatch, cleans up + * after the connection ends. + */ +static DWORD WINAPI pf_client_thread_proc(pClientContext* pc) +{ + freerdp* instance = NULL; + proxyData* pdata = NULL; + DWORD nCount = 0; + DWORD status = 0; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + + WINPR_ASSERT(pc); + + instance = pc->context.instance; + WINPR_ASSERT(instance); + + pdata = pc->pdata; + WINPR_ASSERT(pdata); + /* + * during redirection, freerdp's abort event might be overriden (reset) by the library, after + * the server set it in order to shutdown the connection. it means that the server might signal + * the client to abort, but the library code will override the signal and the client will + * continue its work instead of exiting. That's why the client must wait on `pdata->abort_event` + * too, which will never be modified by the library. + */ + handles[nCount++] = pdata->abort_event; + + if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_INIT_CONNECT, pdata, pc)) + { + proxy_data_abort_connect(pdata); + goto end; + } + + if (!pf_client_connect(instance)) + { + proxy_data_abort_connect(pdata); + goto end; + } + handles[nCount++] = Queue_Event(pc->cached_server_channel_data); + + while (!freerdp_shall_disconnect_context(instance->context)) + { + UINT32 tmp = freerdp_get_event_handles(instance->context, &handles[nCount], + ARRAYSIZE(handles) - nCount); + + if (tmp == 0) + { + PROXY_LOG_ERR(TAG, pc, "freerdp_get_event_handles failed!"); + break; + } + + status = WaitForMultipleObjects(nCount + tmp, handles, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed with %" PRIu32 "", status); + break; + } + + /* abort_event triggered */ + if (status == WAIT_OBJECT_0) + break; + + if (freerdp_shall_disconnect_context(instance->context)) + break; + + if (proxy_data_shall_disconnect(pdata)) + break; + + if (!freerdp_check_event_handles(instance->context)) + { + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + WLog_ERR(TAG, "Failed to check FreeRDP event handles"); + + break; + } + sendQueuedChannelData(pc); + } + + freerdp_disconnect(instance); + +end: + pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_UNINIT_CONNECT, pdata, pc); + + return 0; +} + +static int pf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + const char* str_data = freerdp_get_logon_error_info_data(data); + const char* str_type = freerdp_get_logon_error_info_type(type); + + if (!instance || !instance->context) + return -1; + + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + return 1; +} + +static void pf_client_context_free(freerdp* instance, rdpContext* context) +{ + pClientContext* pc = (pClientContext*)context; + WINPR_UNUSED(instance); + + if (!pc) + return; + + pc->sendChannelData = NULL; + Queue_Free(pc->cached_server_channel_data); + Stream_Free(pc->remote_pem, TRUE); + free(pc->remote_hostname); + free(pc->computerName.v); + HashTable_Free(pc->interceptContextMap); +} + +static int pf_client_verify_X509_certificate(freerdp* instance, const BYTE* data, size_t length, + const char* hostname, UINT16 port, DWORD flags) +{ + pClientContext* pc = NULL; + + WINPR_ASSERT(instance); + WINPR_ASSERT(data); + WINPR_ASSERT(length > 0); + WINPR_ASSERT(hostname); + + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + + if (!Stream_EnsureCapacity(pc->remote_pem, length)) + return 0; + Stream_SetPosition(pc->remote_pem, 0); + + free(pc->remote_hostname); + pc->remote_hostname = NULL; + + if (length > 0) + Stream_Write(pc->remote_pem, data, length); + + if (hostname) + pc->remote_hostname = _strdup(hostname); + pc->remote_port = port; + pc->remote_flags = flags; + + Stream_SealLength(pc->remote_pem); + if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_VERIFY_X509, pc->pdata, pc)) + return 0; + return 1; +} + +void channel_data_free(void* obj) +{ + union + { + const void* cpv; + void* pv; + } cnv; + proxyChannelDataEventInfo* dst = obj; + if (dst) + { + cnv.cpv = dst->data; + free(cnv.pv); + + cnv.cpv = dst->channel_name; + free(cnv.pv); + free(dst); + } +} + +static void* channel_data_copy(const void* obj) +{ + union + { + const void* cpv; + void* pv; + } cnv; + const proxyChannelDataEventInfo* src = obj; + proxyChannelDataEventInfo* dst = NULL; + + WINPR_ASSERT(src); + + dst = calloc(1, sizeof(proxyChannelDataEventInfo)); + if (!dst) + goto fail; + + *dst = *src; + if (src->channel_name) + { + dst->channel_name = _strdup(src->channel_name); + if (!dst->channel_name) + goto fail; + } + dst->data = malloc(src->data_len); + if (!dst->data) + goto fail; + + cnv.cpv = dst->data; + memcpy(cnv.pv, src->data, src->data_len); + return dst; + +fail: + channel_data_free(dst); + return NULL; +} + +static BOOL pf_client_client_new(freerdp* instance, rdpContext* context) +{ + wObject* obj = NULL; + pClientContext* pc = (pClientContext*)context; + + if (!instance || !context) + return FALSE; + + instance->LoadChannels = pf_client_load_channels; + instance->PreConnect = pf_client_pre_connect; + instance->PostConnect = pf_client_post_connect; + instance->PostDisconnect = pf_client_post_disconnect; + instance->Redirect = pf_client_redirect; + instance->LogonErrorInfo = pf_logon_error_info; + instance->VerifyX509Certificate = pf_client_verify_X509_certificate; + + pc->remote_pem = Stream_New(NULL, 4096); + if (!pc->remote_pem) + return FALSE; + + pc->sendChannelData = pf_client_send_channel_data; + pc->cached_server_channel_data = Queue_New(TRUE, -1, -1); + if (!pc->cached_server_channel_data) + return FALSE; + obj = Queue_Object(pc->cached_server_channel_data); + WINPR_ASSERT(obj); + obj->fnObjectNew = channel_data_copy; + obj->fnObjectFree = channel_data_free; + + pc->interceptContextMap = HashTable_New(FALSE); + if (!pc->interceptContextMap) + return FALSE; + + if (!HashTable_SetupForStringData(pc->interceptContextMap, FALSE)) + return FALSE; + + obj = HashTable_ValueObject(pc->interceptContextMap); + WINPR_ASSERT(obj); + obj->fnObjectFree = intercept_context_entry_free; + + return TRUE; +} + +static int pf_client_client_stop(rdpContext* context) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + + PROXY_LOG_DBG(TAG, pc, "aborting client connection"); + proxy_data_abort_connect(pdata); + freerdp_abort_connect_context(context); + + return 0; +} + +int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->ContextSize = sizeof(pClientContext); + /* Client init and finish */ + pEntryPoints->ClientNew = pf_client_client_new; + pEntryPoints->ClientFree = pf_client_context_free; + pEntryPoints->ClientStop = pf_client_client_stop; + return 0; +} + +/** + * Starts running a client connection towards target server. + */ +DWORD WINAPI pf_client_start(LPVOID arg) +{ + DWORD rc = 1; + pClientContext* pc = (pClientContext*)arg; + + WINPR_ASSERT(pc); + if (freerdp_client_start(&pc->context) == 0) + rc = pf_client_thread_proc(pc); + freerdp_client_stop(&pc->context); + return rc; +} diff --git a/server/proxy/pf_client.h b/server/proxy/pf_client.h new file mode 100644 index 0000000..cda87a8 --- /dev/null +++ b/server/proxy/pf_client.h @@ -0,0 +1,31 @@ +/** + * 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> + * + * 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. + */ + +#ifndef FREERDP_SERVER_PROXY_PFCLIENT_H +#define FREERDP_SERVER_PROXY_PFCLIENT_H + +#include <freerdp/freerdp.h> +#include <winpr/wtypes.h> + +int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints); +DWORD WINAPI pf_client_start(LPVOID arg); + +#endif /* FREERDP_SERVER_PROXY_PFCLIENT_H */ diff --git a/server/proxy/pf_config.c b/server/proxy/pf_config.c new file mode 100644 index 0000000..bcce1b1 --- /dev/null +++ b/server/proxy/pf_config.c @@ -0,0 +1,1348 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com> + * Copyright 2019 Idan Freiberg <speidy@gmail.com> + * Copyright 2021,2023 Armin Novak <anovak@thincast.com> + * Copyright 2021,2023 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 <stdio.h> +#include <string.h> +#include <winpr/crt.h> +#include <winpr/path.h> +#include <winpr/collections.h> +#include <winpr/cmdline.h> + +#include "pf_server.h" +#include <freerdp/server/proxy/proxy_config.h> + +#include <freerdp/server/proxy/proxy_log.h> + +#include <freerdp/crypto/crypto.h> +#include <freerdp/channels/cliprdr.h> +#include <freerdp/channels/rdpsnd.h> +#include <freerdp/channels/audin.h> +#include <freerdp/channels/rdpdr.h> +#include <freerdp/channels/disp.h> +#include <freerdp/channels/rail.h> +#include <freerdp/channels/rdpei.h> +#include <freerdp/channels/tsmf.h> +#include <freerdp/channels/video.h> +#include <freerdp/channels/rdpecam.h> + +#include "pf_utils.h" + +#define TAG PROXY_TAG("config") + +#define CONFIG_PRINT_SECTION(section) WLog_INFO(TAG, "\t%s:", section) +#define CONFIG_PRINT_SECTION_KEY(section, key) WLog_INFO(TAG, "\t%s/%s:", section, key) +#define CONFIG_PRINT_STR(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, config->key) +#define CONFIG_PRINT_STR_CONTENT(config, key) \ + WLog_INFO(TAG, "\t\t%s: %s", #key, config->key ? "set" : NULL) +#define CONFIG_PRINT_BOOL(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, boolstr(config->key)) +#define CONFIG_PRINT_UINT16(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu16 "", #key, config->key) +#define CONFIG_PRINT_UINT32(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu32 "", #key, config->key) + +static const char* bool_str_true = "true"; +static const char* bool_str_false = "false"; +static const char* boolstr(BOOL rc) +{ + return rc ? bool_str_true : bool_str_false; +} + +static const char* section_server = "Server"; +static const char* key_host = "Host"; +static const char* key_port = "Port"; + +static const char* section_target = "Target"; +static const char* key_target_fixed = "FixedTarget"; +static const char* key_target_user = "User"; +static const char* key_target_pwd = "Password"; +static const char* key_target_domain = "Domain"; +static const char* key_target_tls_seclevel = "TlsSecLevel"; + +static const char* section_clipboard = "Clipboard"; +static const char* key_clip_text_only = "TextOnly"; +static const char* key_clip_text_max_len = "MaxTextLength"; + +static const char* section_gfx_settings = "GFXSettings"; +static const char* key_gfx_decode = "DecodeGFX"; + +static const char* section_plugins = "Plugins"; +static const char* key_plugins_modules = "Modules"; +static const char* key_plugins_required = "Required"; + +static const char* section_channels = "Channels"; +static const char* key_channels_gfx = "GFX"; +static const char* key_channels_disp = "DisplayControl"; +static const char* key_channels_clip = "Clipboard"; +static const char* key_channels_mic = "AudioInput"; +static const char* key_channels_sound = "AudioOutput"; +static const char* key_channels_rdpdr = "DeviceRedirection"; +static const char* key_channels_video = "VideoRedirection"; +static const char* key_channels_camera = "CameraRedirection"; +static const char* key_channels_rails = "RemoteApp"; +static const char* key_channels_blacklist = "PassthroughIsBlacklist"; +static const char* key_channels_pass = "Passthrough"; +static const char* key_channels_intercept = "Intercept"; + +static const char* section_input = "Input"; +static const char* key_input_kbd = "Keyboard"; +static const char* key_input_mouse = "Mouse"; +static const char* key_input_multitouch = "Multitouch"; + +static const char* section_security = "Security"; +static const char* key_security_server_nla = "ServerNlaSecurity"; +static const char* key_security_server_tls = "ServerTlsSecurity"; +static const char* key_security_server_rdp = "ServerRdpSecurity"; +static const char* key_security_client_nla = "ClientNlaSecurity"; +static const char* key_security_client_tls = "ClientTlsSecurity"; +static const char* key_security_client_rdp = "ClientRdpSecurity"; +static const char* key_security_client_fallback = "ClientAllowFallbackToTls"; + +static const char* section_certificates = "Certificates"; +static const char* key_private_key_file = "PrivateKeyFile"; +static const char* key_private_key_content = "PrivateKeyContent"; +static const char* key_cert_file = "CertificateFile"; +static const char* key_cert_content = "CertificateContent"; + +static char** pf_config_parse_comma_separated_list(const char* list, size_t* count) +{ + if (!list || !count) + return NULL; + + if (strlen(list) == 0) + { + *count = 0; + return NULL; + } + + return CommandLineParseCommaSeparatedValues(list, count); +} + +static BOOL pf_config_get_uint16(wIniFile* ini, const char* section, const char* key, + UINT16* result, BOOL required) +{ + int val = 0; + const char* strval = NULL; + + WINPR_ASSERT(result); + + strval = IniFile_GetKeyValueString(ini, section, key); + if (!strval && required) + { + WLog_ERR(TAG, "key '%s.%s' does not exist.", section, key); + return FALSE; + } + val = IniFile_GetKeyValueInt(ini, section, key); + if ((val <= 0) || (val > UINT16_MAX)) + { + WLog_ERR(TAG, "invalid value %d for key '%s.%s'.", val, section, key); + return FALSE; + } + + *result = (UINT16)val; + return TRUE; +} + +static BOOL pf_config_get_uint32(wIniFile* ini, const char* section, const char* key, + UINT32* result, BOOL required) +{ + int val = 0; + const char* strval = NULL; + + WINPR_ASSERT(result); + + strval = IniFile_GetKeyValueString(ini, section, key); + if (!strval) + { + if (required) + WLog_ERR(TAG, "key '%s.%s' does not exist.", section, key); + return !required; + } + + val = IniFile_GetKeyValueInt(ini, section, key); + if ((val < 0) || (val > INT32_MAX)) + { + WLog_ERR(TAG, "invalid value %d for key '%s.%s'.", val, section, key); + return FALSE; + } + + *result = (UINT32)val; + return TRUE; +} + +static BOOL pf_config_get_bool(wIniFile* ini, const char* section, const char* key, BOOL fallback) +{ + int num_value = 0; + const char* str_value = NULL; + + str_value = IniFile_GetKeyValueString(ini, section, key); + if (!str_value) + { + WLog_WARN(TAG, "key '%s.%s' not found, value defaults to %s.", section, key, + fallback ? bool_str_true : bool_str_false); + return fallback; + } + + if (_stricmp(str_value, bool_str_true) == 0) + return TRUE; + if (_stricmp(str_value, bool_str_false) == 0) + return FALSE; + + num_value = IniFile_GetKeyValueInt(ini, section, key); + + if (num_value != 0) + return TRUE; + + return FALSE; +} + +static const char* pf_config_get_str(wIniFile* ini, const char* section, const char* key, + BOOL required) +{ + const char* value = NULL; + + value = IniFile_GetKeyValueString(ini, section, key); + + if (!value) + { + if (required) + WLog_ERR(TAG, "key '%s.%s' not found.", section, key); + return NULL; + } + + return value; +} + +static BOOL pf_config_load_server(wIniFile* ini, proxyConfig* config) +{ + const char* host = NULL; + + WINPR_ASSERT(config); + host = pf_config_get_str(ini, section_server, key_host, FALSE); + + if (!host) + return TRUE; + + config->Host = _strdup(host); + + if (!config->Host) + return FALSE; + + if (!pf_config_get_uint16(ini, section_server, key_port, &config->Port, TRUE)) + return FALSE; + + return TRUE; +} + +static BOOL pf_config_load_target(wIniFile* ini, proxyConfig* config) +{ + const char* target_value = NULL; + + WINPR_ASSERT(config); + config->FixedTarget = pf_config_get_bool(ini, section_target, key_target_fixed, FALSE); + + if (!pf_config_get_uint16(ini, section_target, key_port, &config->TargetPort, + config->FixedTarget)) + return FALSE; + + if (!pf_config_get_uint32(ini, section_target, key_target_tls_seclevel, + &config->TargetTlsSecLevel, FALSE)) + return FALSE; + + if (config->FixedTarget) + { + target_value = pf_config_get_str(ini, section_target, key_host, TRUE); + if (!target_value) + return FALSE; + + config->TargetHost = _strdup(target_value); + if (!config->TargetHost) + return FALSE; + } + + target_value = pf_config_get_str(ini, section_target, key_target_user, FALSE); + if (target_value) + { + config->TargetUser = _strdup(target_value); + if (!config->TargetUser) + return FALSE; + } + + target_value = pf_config_get_str(ini, section_target, key_target_pwd, FALSE); + if (target_value) + { + config->TargetPassword = _strdup(target_value); + if (!config->TargetPassword) + return FALSE; + } + + target_value = pf_config_get_str(ini, section_target, key_target_domain, FALSE); + if (target_value) + { + config->TargetDomain = _strdup(target_value); + if (!config->TargetDomain) + return FALSE; + } + + return TRUE; +} + +static BOOL pf_config_load_channels(wIniFile* ini, proxyConfig* config) +{ + WINPR_ASSERT(config); + config->GFX = pf_config_get_bool(ini, section_channels, key_channels_gfx, TRUE); + config->DisplayControl = pf_config_get_bool(ini, section_channels, key_channels_disp, TRUE); + config->Clipboard = pf_config_get_bool(ini, section_channels, key_channels_clip, FALSE); + config->AudioOutput = pf_config_get_bool(ini, section_channels, key_channels_mic, TRUE); + config->AudioInput = pf_config_get_bool(ini, section_channels, key_channels_sound, TRUE); + config->DeviceRedirection = pf_config_get_bool(ini, section_channels, key_channels_rdpdr, TRUE); + config->VideoRedirection = pf_config_get_bool(ini, section_channels, key_channels_video, TRUE); + config->CameraRedirection = + pf_config_get_bool(ini, section_channels, key_channels_camera, TRUE); + config->RemoteApp = pf_config_get_bool(ini, section_channels, key_channels_rails, FALSE); + config->PassthroughIsBlacklist = + pf_config_get_bool(ini, section_channels, key_channels_blacklist, FALSE); + config->Passthrough = pf_config_parse_comma_separated_list( + pf_config_get_str(ini, section_channels, key_channels_pass, FALSE), + &config->PassthroughCount); + config->Intercept = pf_config_parse_comma_separated_list( + pf_config_get_str(ini, section_channels, key_channels_intercept, FALSE), + &config->InterceptCount); + + return TRUE; +} + +static BOOL pf_config_load_input(wIniFile* ini, proxyConfig* config) +{ + WINPR_ASSERT(config); + config->Keyboard = pf_config_get_bool(ini, section_input, key_input_kbd, TRUE); + config->Mouse = pf_config_get_bool(ini, section_input, key_input_mouse, TRUE); + config->Multitouch = pf_config_get_bool(ini, section_input, key_input_multitouch, TRUE); + return TRUE; +} + +static BOOL pf_config_load_security(wIniFile* ini, proxyConfig* config) +{ + WINPR_ASSERT(config); + config->ServerTlsSecurity = + pf_config_get_bool(ini, section_security, key_security_server_tls, TRUE); + config->ServerNlaSecurity = + pf_config_get_bool(ini, section_security, key_security_server_nla, FALSE); + config->ServerRdpSecurity = + pf_config_get_bool(ini, section_security, key_security_server_rdp, TRUE); + + config->ClientTlsSecurity = + pf_config_get_bool(ini, section_security, key_security_client_tls, TRUE); + config->ClientNlaSecurity = + pf_config_get_bool(ini, section_security, key_security_client_nla, TRUE); + config->ClientRdpSecurity = + pf_config_get_bool(ini, section_security, key_security_client_rdp, TRUE); + config->ClientAllowFallbackToTls = + pf_config_get_bool(ini, section_security, key_security_client_fallback, TRUE); + return TRUE; +} + +static BOOL pf_config_load_clipboard(wIniFile* ini, proxyConfig* config) +{ + WINPR_ASSERT(config); + config->TextOnly = pf_config_get_bool(ini, section_clipboard, key_clip_text_only, FALSE); + + if (!pf_config_get_uint32(ini, section_clipboard, key_clip_text_max_len, &config->MaxTextLength, + FALSE)) + return FALSE; + + return TRUE; +} + +static BOOL pf_config_load_modules(wIniFile* ini, proxyConfig* config) +{ + const char* modules_to_load = NULL; + const char* required_modules = NULL; + + modules_to_load = pf_config_get_str(ini, section_plugins, key_plugins_modules, FALSE); + required_modules = pf_config_get_str(ini, section_plugins, key_plugins_required, FALSE); + + WINPR_ASSERT(config); + config->Modules = pf_config_parse_comma_separated_list(modules_to_load, &config->ModulesCount); + + config->RequiredPlugins = + pf_config_parse_comma_separated_list(required_modules, &config->RequiredPluginsCount); + return TRUE; +} + +static BOOL pf_config_load_gfx_settings(wIniFile* ini, proxyConfig* config) +{ + WINPR_ASSERT(config); + config->DecodeGFX = pf_config_get_bool(ini, section_gfx_settings, key_gfx_decode, FALSE); + return TRUE; +} + +static char* pf_config_decode_base64(const char* data, const char* name, size_t* pLength) +{ + const char* headers[] = { "-----BEGIN PUBLIC KEY-----", "-----BEGIN RSA PUBLIC KEY-----", + "-----BEGIN CERTIFICATE-----", "-----BEGIN PRIVATE KEY-----", + "-----BEGIN RSA PRIVATE KEY-----" }; + + size_t decoded_length = 0; + char* decoded = NULL; + if (!data) + { + WLog_ERR(TAG, "Invalid base64 data [%p] for %s", data, name); + return NULL; + } + + WINPR_ASSERT(name); + WINPR_ASSERT(pLength); + + const size_t length = strlen(data); + + if (strncmp(data, "-----", 5) == 0) + { + BOOL expected = FALSE; + for (size_t x = 0; x < ARRAYSIZE(headers); x++) + { + const char* header = headers[x]; + + if (strncmp(data, header, strlen(header)) == 0) + expected = TRUE; + } + + if (!expected) + { + /* Extract header for log message + * expected format is '----- SOMETEXT -----' + */ + char hdr[128] = { 0 }; + const char* end = strchr(&data[5], '-'); + if (end) + { + while (*end == '-') + end++; + + const size_t s = MIN(ARRAYSIZE(hdr) - 1, end - data); + memcpy(hdr, data, s); + } + + WLog_WARN(TAG, "PEM has unexpected header '%s'. Known supported headers are:", hdr); + for (size_t x = 0; x < ARRAYSIZE(headers); x++) + { + const char* header = headers[x]; + WLog_WARN(TAG, "%s", header); + } + } + + *pLength = length + 1; + return _strdup(data); + } + + crypto_base64_decode(data, length, (BYTE**)&decoded, &decoded_length); + if (!decoded || decoded_length == 0) + { + WLog_ERR(TAG, "Failed to decode base64 data of length %" PRIuz " for %s", length, name); + free(decoded); + return NULL; + } + + *pLength = strnlen(decoded, decoded_length) + 1; + return decoded; +} + +static BOOL pf_config_load_certificates(wIniFile* ini, proxyConfig* config) +{ + const char* tmp1 = NULL; + const char* tmp2 = NULL; + + WINPR_ASSERT(ini); + WINPR_ASSERT(config); + + tmp1 = pf_config_get_str(ini, section_certificates, key_cert_file, FALSE); + if (tmp1) + { + if (!winpr_PathFileExists(tmp1)) + { + WLog_ERR(TAG, "%s/%s file %s does not exist", section_certificates, key_cert_file, + tmp1); + return FALSE; + } + config->CertificateFile = _strdup(tmp1); + config->CertificatePEM = + crypto_read_pem(config->CertificateFile, &config->CertificatePEMLength); + if (!config->CertificatePEM) + return FALSE; + config->CertificatePEMLength += 1; + } + tmp2 = pf_config_get_str(ini, section_certificates, key_cert_content, FALSE); + if (tmp2) + { + if (strlen(tmp2) < 1) + { + WLog_ERR(TAG, "%s/%s has invalid empty value", section_certificates, key_cert_content); + return FALSE; + } + config->CertificateContent = _strdup(tmp2); + config->CertificatePEM = pf_config_decode_base64( + config->CertificateContent, "CertificateContent", &config->CertificatePEMLength); + if (!config->CertificatePEM) + return FALSE; + } + if (tmp1 && tmp2) + { + WLog_ERR(TAG, + "%s/%s and %s/%s are " + "mutually exclusive options", + section_certificates, key_cert_file, section_certificates, key_cert_content); + return FALSE; + } + else if (!tmp1 && !tmp2) + { + WLog_ERR(TAG, + "%s/%s or %s/%s are " + "required settings", + section_certificates, key_cert_file, section_certificates, key_cert_content); + return FALSE; + } + + tmp1 = pf_config_get_str(ini, section_certificates, key_private_key_file, FALSE); + if (tmp1) + { + if (!winpr_PathFileExists(tmp1)) + { + WLog_ERR(TAG, "%s/%s file %s does not exist", section_certificates, + key_private_key_file, tmp1); + return FALSE; + } + config->PrivateKeyFile = _strdup(tmp1); + config->PrivateKeyPEM = + crypto_read_pem(config->PrivateKeyFile, &config->PrivateKeyPEMLength); + if (!config->PrivateKeyPEM) + return FALSE; + config->PrivateKeyPEMLength += 1; + } + tmp2 = pf_config_get_str(ini, section_certificates, key_private_key_content, FALSE); + if (tmp2) + { + if (strlen(tmp2) < 1) + { + WLog_ERR(TAG, "%s/%s has invalid empty value", section_certificates, + key_private_key_content); + return FALSE; + } + config->PrivateKeyContent = _strdup(tmp2); + config->PrivateKeyPEM = pf_config_decode_base64( + config->PrivateKeyContent, "PrivateKeyContent", &config->PrivateKeyPEMLength); + if (!config->PrivateKeyPEM) + return FALSE; + } + + if (tmp1 && tmp2) + { + WLog_ERR(TAG, + "%s/%s and %s/%s are " + "mutually exclusive options", + section_certificates, key_private_key_file, section_certificates, + key_private_key_content); + return FALSE; + } + else if (!tmp1 && !tmp2) + { + WLog_ERR(TAG, + "%s/%s or %s/%s are " + "are required settings", + section_certificates, key_private_key_file, section_certificates, + key_private_key_content); + return FALSE; + } + + return TRUE; +} + +proxyConfig* server_config_load_ini(wIniFile* ini) +{ + proxyConfig* config = NULL; + + WINPR_ASSERT(ini); + + config = calloc(1, sizeof(proxyConfig)); + if (config) + { + /* Set default values != 0 */ + config->TargetTlsSecLevel = 1; + + /* Load from ini */ + if (!pf_config_load_server(ini, config)) + goto out; + + if (!pf_config_load_target(ini, config)) + goto out; + + if (!pf_config_load_channels(ini, config)) + goto out; + + if (!pf_config_load_input(ini, config)) + goto out; + + if (!pf_config_load_security(ini, config)) + goto out; + + if (!pf_config_load_modules(ini, config)) + goto out; + + if (!pf_config_load_clipboard(ini, config)) + goto out; + + if (!pf_config_load_gfx_settings(ini, config)) + goto out; + + if (!pf_config_load_certificates(ini, config)) + goto out; + config->ini = IniFile_Clone(ini); + if (!config->ini) + goto out; + } + return config; +out: + pf_server_config_free(config); + return NULL; +} + +BOOL pf_server_config_dump(const char* file) +{ + BOOL rc = FALSE; + wIniFile* ini = IniFile_New(); + if (!ini) + return FALSE; + + /* Proxy server configuration */ + if (IniFile_SetKeyValueString(ini, section_server, key_host, "0.0.0.0") < 0) + goto fail; + if (IniFile_SetKeyValueInt(ini, section_server, key_port, 3389) < 0) + goto fail; + + /* Target configuration */ + if (IniFile_SetKeyValueString(ini, section_target, key_host, "somehost.example.com") < 0) + goto fail; + if (IniFile_SetKeyValueInt(ini, section_target, key_port, 3389) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_target, key_target_fixed, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueInt(ini, section_target, key_target_tls_seclevel, 1) < 0) + goto fail; + + /* Channel configuration */ + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_gfx, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_disp, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_clip, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_mic, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_sound, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_rdpdr, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_video, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_camera, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_rails, bool_str_false) < 0) + goto fail; + + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_blacklist, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_pass, "") < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_channels, key_channels_intercept, "") < 0) + goto fail; + + /* Input configuration */ + if (IniFile_SetKeyValueString(ini, section_input, key_input_kbd, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_input, key_input_mouse, bool_str_true) < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_input, key_input_multitouch, bool_str_true) < 0) + goto fail; + + /* Security settings */ + if (IniFile_SetKeyValueString(ini, section_security, key_security_server_tls, bool_str_true) < + 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_security, key_security_server_nla, bool_str_false) < + 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_security, key_security_server_rdp, bool_str_true) < + 0) + goto fail; + + if (IniFile_SetKeyValueString(ini, section_security, key_security_client_tls, bool_str_true) < + 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_security, key_security_client_nla, bool_str_true) < + 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_security, key_security_client_rdp, bool_str_true) < + 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_security, key_security_client_fallback, + bool_str_true) < 0) + goto fail; + + /* Module configuration */ + if (IniFile_SetKeyValueString(ini, section_plugins, key_plugins_modules, + "module1,module2,...") < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_plugins, key_plugins_required, + "module1,module2,...") < 0) + goto fail; + + /* Clipboard configuration */ + if (IniFile_SetKeyValueString(ini, section_clipboard, key_clip_text_only, bool_str_false) < 0) + goto fail; + if (IniFile_SetKeyValueInt(ini, section_clipboard, key_clip_text_max_len, 0) < 0) + goto fail; + + /* GFX configuration */ + if (IniFile_SetKeyValueString(ini, section_gfx_settings, key_gfx_decode, bool_str_false) < 0) + goto fail; + + /* Certificate configuration */ + if (IniFile_SetKeyValueString(ini, section_certificates, key_cert_file, + "<absolute path to some certificate file> OR") < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_certificates, key_cert_content, + "<Contents of some certificate file in PEM format>") < 0) + goto fail; + + if (IniFile_SetKeyValueString(ini, section_certificates, key_private_key_file, + "<absolute path to some private key file> OR") < 0) + goto fail; + if (IniFile_SetKeyValueString(ini, section_certificates, key_private_key_content, + "<Contents of some private key file in PEM format>") < 0) + goto fail; + + /* store configuration */ + if (IniFile_WriteFile(ini, file) < 0) + goto fail; + + rc = TRUE; + +fail: + IniFile_Free(ini); + return rc; +} + +proxyConfig* pf_server_config_load_buffer(const char* buffer) +{ + proxyConfig* config = NULL; + wIniFile* ini = NULL; + + ini = IniFile_New(); + + if (!ini) + { + WLog_ERR(TAG, "IniFile_New() failed!"); + return NULL; + } + + if (IniFile_ReadBuffer(ini, buffer) < 0) + { + WLog_ERR(TAG, "failed to parse ini: '%s'", buffer); + goto out; + } + + config = server_config_load_ini(ini); +out: + IniFile_Free(ini); + return config; +} + +proxyConfig* pf_server_config_load_file(const char* path) +{ + proxyConfig* config = NULL; + wIniFile* ini = IniFile_New(); + + if (!ini) + { + WLog_ERR(TAG, "IniFile_New() failed!"); + return NULL; + } + + if (IniFile_ReadFile(ini, path) < 0) + { + WLog_ERR(TAG, "failed to parse ini file: '%s'", path); + goto out; + } + + config = server_config_load_ini(ini); +out: + IniFile_Free(ini); + return config; +} + +static void pf_server_config_print_list(char** list, size_t count) +{ + WINPR_ASSERT(list); + for (size_t i = 0; i < count; i++) + WLog_INFO(TAG, "\t\t- %s", list[i]); +} + +void pf_server_config_print(const proxyConfig* config) +{ + WINPR_ASSERT(config); + WLog_INFO(TAG, "Proxy configuration:"); + + CONFIG_PRINT_SECTION(section_server); + CONFIG_PRINT_STR(config, Host); + CONFIG_PRINT_UINT16(config, Port); + + if (config->FixedTarget) + { + CONFIG_PRINT_SECTION(section_target); + CONFIG_PRINT_STR(config, TargetHost); + CONFIG_PRINT_UINT16(config, TargetPort); + CONFIG_PRINT_UINT32(config, TargetTlsSecLevel); + + if (config->TargetUser) + CONFIG_PRINT_STR(config, TargetUser); + if (config->TargetDomain) + CONFIG_PRINT_STR(config, TargetDomain); + } + + CONFIG_PRINT_SECTION(section_input); + CONFIG_PRINT_BOOL(config, Keyboard); + CONFIG_PRINT_BOOL(config, Mouse); + CONFIG_PRINT_BOOL(config, Multitouch); + + CONFIG_PRINT_SECTION(section_security); + CONFIG_PRINT_BOOL(config, ServerNlaSecurity); + CONFIG_PRINT_BOOL(config, ServerTlsSecurity); + CONFIG_PRINT_BOOL(config, ServerRdpSecurity); + CONFIG_PRINT_BOOL(config, ClientNlaSecurity); + CONFIG_PRINT_BOOL(config, ClientTlsSecurity); + CONFIG_PRINT_BOOL(config, ClientRdpSecurity); + CONFIG_PRINT_BOOL(config, ClientAllowFallbackToTls); + + CONFIG_PRINT_SECTION(section_channels); + CONFIG_PRINT_BOOL(config, GFX); + CONFIG_PRINT_BOOL(config, DisplayControl); + CONFIG_PRINT_BOOL(config, Clipboard); + CONFIG_PRINT_BOOL(config, AudioOutput); + CONFIG_PRINT_BOOL(config, AudioInput); + CONFIG_PRINT_BOOL(config, DeviceRedirection); + CONFIG_PRINT_BOOL(config, VideoRedirection); + CONFIG_PRINT_BOOL(config, CameraRedirection); + CONFIG_PRINT_BOOL(config, RemoteApp); + CONFIG_PRINT_BOOL(config, PassthroughIsBlacklist); + + if (config->PassthroughCount) + { + WLog_INFO(TAG, "\tStatic Channels Proxy:"); + pf_server_config_print_list(config->Passthrough, config->PassthroughCount); + } + + if (config->InterceptCount) + { + WLog_INFO(TAG, "\tStatic Channels Proxy-Intercept:"); + pf_server_config_print_list(config->Intercept, config->InterceptCount); + } + + CONFIG_PRINT_SECTION(section_clipboard); + CONFIG_PRINT_BOOL(config, TextOnly); + if (config->MaxTextLength > 0) + CONFIG_PRINT_UINT32(config, MaxTextLength); + + CONFIG_PRINT_SECTION(section_gfx_settings); + CONFIG_PRINT_BOOL(config, DecodeGFX); + + /* modules */ + CONFIG_PRINT_SECTION_KEY(section_plugins, key_plugins_modules); + for (size_t x = 0; x < config->ModulesCount; x++) + CONFIG_PRINT_STR(config, Modules[x]); + + /* Required plugins */ + CONFIG_PRINT_SECTION_KEY(section_plugins, key_plugins_required); + for (size_t x = 0; x < config->RequiredPluginsCount; x++) + CONFIG_PRINT_STR(config, RequiredPlugins[x]); + + CONFIG_PRINT_SECTION(section_certificates); + CONFIG_PRINT_STR(config, CertificateFile); + CONFIG_PRINT_STR_CONTENT(config, CertificateContent); + CONFIG_PRINT_STR(config, PrivateKeyFile); + CONFIG_PRINT_STR_CONTENT(config, PrivateKeyContent); +} + +void pf_server_config_free(proxyConfig* config) +{ + if (config == NULL) + return; + + free(config->Passthrough); + free(config->Intercept); + free(config->RequiredPlugins); + free(config->Modules); + free(config->TargetHost); + free(config->Host); + free(config->CertificateFile); + free(config->CertificateContent); + if (config->CertificatePEM) + memset(config->CertificatePEM, 0, config->CertificatePEMLength); + free(config->CertificatePEM); + free(config->PrivateKeyFile); + free(config->PrivateKeyContent); + if (config->PrivateKeyPEM) + memset(config->PrivateKeyPEM, 0, config->PrivateKeyPEMLength); + free(config->PrivateKeyPEM); + IniFile_Free(config->ini); + free(config); +} + +size_t pf_config_required_plugins_count(const proxyConfig* config) +{ + WINPR_ASSERT(config); + return config->RequiredPluginsCount; +} + +const char* pf_config_required_plugin(const proxyConfig* config, size_t index) +{ + WINPR_ASSERT(config); + if (index >= config->RequiredPluginsCount) + return NULL; + + return config->RequiredPlugins[index]; +} + +size_t pf_config_modules_count(const proxyConfig* config) +{ + WINPR_ASSERT(config); + return config->ModulesCount; +} + +const char** pf_config_modules(const proxyConfig* config) +{ + union + { + char** ppc; + const char** cppc; + } cnv; + + WINPR_ASSERT(config); + + cnv.ppc = config->Modules; + return cnv.cppc; +} + +static BOOL pf_config_copy_string(char** dst, const char* src) +{ + *dst = NULL; + if (src) + *dst = _strdup(src); + return TRUE; +} + +static BOOL pf_config_copy_string_n(char** dst, const char* src, size_t size) +{ + *dst = NULL; + + if (src && (size > 0)) + { + WINPR_ASSERT(strnlen(src, size) == size - 1); + *dst = calloc(size, sizeof(char)); + if (!*dst) + return FALSE; + memcpy(*dst, src, size); + } + + return TRUE; +} + +static BOOL pf_config_copy_string_list(char*** dst, size_t* size, char** src, size_t srcSize) +{ + WINPR_ASSERT(dst); + WINPR_ASSERT(size); + WINPR_ASSERT(src || (srcSize == 0)); + + *dst = NULL; + *size = 0; + if (srcSize == 0) + return TRUE; + { + char* csv = CommandLineToCommaSeparatedValues(srcSize, src); + *dst = CommandLineParseCommaSeparatedValues(csv, size); + free(csv); + } + + return TRUE; +} + +BOOL pf_config_clone(proxyConfig** dst, const proxyConfig* config) +{ + proxyConfig* tmp = calloc(1, sizeof(proxyConfig)); + + WINPR_ASSERT(dst); + WINPR_ASSERT(config); + + if (!tmp) + return FALSE; + + *tmp = *config; + + if (!pf_config_copy_string(&tmp->Host, config->Host)) + goto fail; + if (!pf_config_copy_string(&tmp->TargetHost, config->TargetHost)) + goto fail; + + if (!pf_config_copy_string_list(&tmp->Passthrough, &tmp->PassthroughCount, config->Passthrough, + config->PassthroughCount)) + goto fail; + if (!pf_config_copy_string_list(&tmp->Intercept, &tmp->InterceptCount, config->Intercept, + config->InterceptCount)) + goto fail; + if (!pf_config_copy_string_list(&tmp->Modules, &tmp->ModulesCount, config->Modules, + config->ModulesCount)) + goto fail; + if (!pf_config_copy_string_list(&tmp->RequiredPlugins, &tmp->RequiredPluginsCount, + config->RequiredPlugins, config->RequiredPluginsCount)) + goto fail; + if (!pf_config_copy_string(&tmp->CertificateFile, config->CertificateFile)) + goto fail; + if (!pf_config_copy_string(&tmp->CertificateContent, config->CertificateContent)) + goto fail; + if (!pf_config_copy_string_n(&tmp->CertificatePEM, config->CertificatePEM, + config->CertificatePEMLength)) + goto fail; + if (!pf_config_copy_string(&tmp->PrivateKeyFile, config->PrivateKeyFile)) + goto fail; + if (!pf_config_copy_string(&tmp->PrivateKeyContent, config->PrivateKeyContent)) + goto fail; + if (!pf_config_copy_string_n(&tmp->PrivateKeyPEM, config->PrivateKeyPEM, + config->PrivateKeyPEMLength)) + goto fail; + + tmp->ini = IniFile_Clone(config->ini); + if (!tmp->ini) + goto fail; + + *dst = tmp; + return TRUE; + +fail: + pf_server_config_free(tmp); + return FALSE; +} + +struct config_plugin_data +{ + proxyPluginsManager* mgr; + const proxyConfig* config; +}; + +static const char config_plugin_name[] = "config"; +static const char config_plugin_desc[] = + "A plugin filtering according to proxy configuration file rules"; + +static BOOL config_plugin_unload(proxyPlugin* plugin) +{ + WINPR_ASSERT(plugin); + + /* Here we have to free up our custom data storage. */ + if (plugin) + { + free(plugin->custom); + plugin->custom = NULL; + } + + return TRUE; +} + +static BOOL config_plugin_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + BOOL rc = 0; + const struct config_plugin_data* custom = NULL; + const proxyConfig* cfg = NULL; + const proxyKeyboardEventInfo* event_data = (const proxyKeyboardEventInfo*)(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WINPR_UNUSED(event_data); + + custom = plugin->custom; + WINPR_ASSERT(custom); + + cfg = custom->config; + WINPR_ASSERT(cfg); + + rc = cfg->Keyboard; + WLog_DBG(TAG, "%s", boolstr(rc)); + return rc; +} + +static BOOL config_plugin_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + BOOL rc = 0; + const struct config_plugin_data* custom = NULL; + const proxyConfig* cfg = NULL; + const proxyUnicodeEventInfo* event_data = (const proxyUnicodeEventInfo*)(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WINPR_UNUSED(event_data); + + custom = plugin->custom; + WINPR_ASSERT(custom); + + cfg = custom->config; + WINPR_ASSERT(cfg); + + rc = cfg->Keyboard; + WLog_DBG(TAG, "%s", boolstr(rc)); + return rc; +} + +static BOOL config_plugin_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + BOOL rc = 0; + const struct config_plugin_data* custom = NULL; + const proxyConfig* cfg = NULL; + const proxyMouseEventInfo* event_data = (const proxyMouseEventInfo*)(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WINPR_UNUSED(event_data); + + custom = plugin->custom; + WINPR_ASSERT(custom); + + cfg = custom->config; + WINPR_ASSERT(cfg); + + rc = cfg->Mouse; + return rc; +} + +static BOOL config_plugin_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + BOOL rc = 0; + const struct config_plugin_data* custom = NULL; + const proxyConfig* cfg = NULL; + const proxyMouseExEventInfo* event_data = (const proxyMouseExEventInfo*)(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(event_data); + + WINPR_UNUSED(event_data); + + custom = plugin->custom; + WINPR_ASSERT(custom); + + cfg = custom->config; + WINPR_ASSERT(cfg); + + rc = cfg->Mouse; + return rc; +} + +static BOOL config_plugin_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + WLog_DBG(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id, + channel->data_len); + return TRUE; +} + +static BOOL config_plugin_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + WLog_DBG(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id, + channel->data_len); + return TRUE; +} + +static BOOL config_plugin_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + BOOL accept = 0; + const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + const struct config_plugin_data* custom = plugin->custom; + WINPR_ASSERT(custom); + + const proxyConfig* cfg = custom->config; + WINPR_ASSERT(cfg); + + pf_utils_channel_mode rc = pf_utils_get_channel_mode(cfg, channel->channel_name); + switch (rc) + { + + case PF_UTILS_CHANNEL_INTERCEPT: + case PF_UTILS_CHANNEL_PASSTHROUGH: + accept = TRUE; + break; + case PF_UTILS_CHANNEL_BLOCK: + default: + accept = FALSE; + break; + } + + if (accept) + { + if (strncmp(RDPGFX_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(RDPGFX_DVC_CHANNEL_NAME)) == 0) + accept = cfg->GFX; + else if (strncmp(RDPSND_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(RDPSND_DVC_CHANNEL_NAME)) == 0) + accept = cfg->AudioOutput; + else if (strncmp(RDPSND_LOSSY_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(RDPSND_LOSSY_DVC_CHANNEL_NAME)) == 0) + accept = cfg->AudioOutput; + else if (strncmp(AUDIN_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(AUDIN_DVC_CHANNEL_NAME)) == 0) + accept = cfg->AudioInput; + else if (strncmp(RDPEI_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(RDPEI_DVC_CHANNEL_NAME)) == 0) + accept = cfg->Multitouch; + else if (strncmp(TSMF_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(TSMF_DVC_CHANNEL_NAME)) == 0) + accept = cfg->VideoRedirection; + else if (strncmp(VIDEO_CONTROL_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(VIDEO_CONTROL_DVC_CHANNEL_NAME)) == 0) + accept = cfg->VideoRedirection; + else if (strncmp(VIDEO_DATA_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(VIDEO_DATA_DVC_CHANNEL_NAME)) == 0) + accept = cfg->VideoRedirection; + else if (strncmp(RDPECAM_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(RDPECAM_DVC_CHANNEL_NAME)) == 0) + accept = cfg->CameraRedirection; + } + + WLog_DBG(TAG, "%s [0x%04" PRIx16 "]: %s", channel->channel_name, channel->channel_id, + boolstr(accept)); + return accept; +} + +static BOOL config_plugin_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param) +{ + BOOL accept = 0; + const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param); + + WINPR_ASSERT(plugin); + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + const struct config_plugin_data* custom = plugin->custom; + WINPR_ASSERT(custom); + + const proxyConfig* cfg = custom->config; + WINPR_ASSERT(cfg); + + pf_utils_channel_mode rc = pf_utils_get_channel_mode(cfg, channel->channel_name); + switch (rc) + { + case PF_UTILS_CHANNEL_INTERCEPT: + case PF_UTILS_CHANNEL_PASSTHROUGH: + accept = TRUE; + break; + case PF_UTILS_CHANNEL_BLOCK: + default: + accept = FALSE; + break; + } + if (accept) + { + if (strncmp(CLIPRDR_SVC_CHANNEL_NAME, channel->channel_name, + sizeof(CLIPRDR_SVC_CHANNEL_NAME)) == 0) + accept = cfg->Clipboard; + else if (strncmp(RDPSND_CHANNEL_NAME, channel->channel_name, sizeof(RDPSND_CHANNEL_NAME)) == + 0) + accept = cfg->AudioOutput; + else if (strncmp(RDPDR_SVC_CHANNEL_NAME, channel->channel_name, + sizeof(RDPDR_SVC_CHANNEL_NAME)) == 0) + accept = cfg->DeviceRedirection; + else if (strncmp(DISP_DVC_CHANNEL_NAME, channel->channel_name, + sizeof(DISP_DVC_CHANNEL_NAME)) == 0) + accept = cfg->DisplayControl; + else if (strncmp(RAIL_SVC_CHANNEL_NAME, channel->channel_name, + sizeof(RAIL_SVC_CHANNEL_NAME)) == 0) + accept = cfg->RemoteApp; + } + + WLog_DBG(TAG, "%s [static]: %s", channel->channel_name, boolstr(accept)); + return accept; +} + +BOOL pf_config_plugin(proxyPluginsManager* plugins_manager, void* userdata) +{ + struct config_plugin_data* custom = NULL; + proxyPlugin plugin = { 0 }; + + plugin.name = config_plugin_name; + plugin.description = config_plugin_desc; + plugin.PluginUnload = config_plugin_unload; + + plugin.KeyboardEvent = config_plugin_keyboard_event; + plugin.UnicodeEvent = config_plugin_unicode_event; + plugin.MouseEvent = config_plugin_mouse_event; + plugin.MouseExEvent = config_plugin_mouse_ex_event; + plugin.ClientChannelData = config_plugin_client_channel_data; + plugin.ServerChannelData = config_plugin_server_channel_data; + plugin.ChannelCreate = config_plugin_channel_create; + plugin.DynamicChannelCreate = config_plugin_dynamic_channel_create; + plugin.userdata = userdata; + + custom = calloc(1, sizeof(struct config_plugin_data)); + if (!custom) + return FALSE; + + custom->mgr = plugins_manager; + custom->config = userdata; + + plugin.custom = custom; + plugin.userdata = userdata; + + return plugins_manager->RegisterPlugin(plugins_manager, &plugin); +} + +const char* pf_config_get(const proxyConfig* config, const char* section, const char* key) +{ + WINPR_ASSERT(config); + WINPR_ASSERT(config->ini); + WINPR_ASSERT(section); + WINPR_ASSERT(key); + + return IniFile_GetKeyValueString(config->ini, section, key); +} diff --git a/server/proxy/pf_context.c b/server/proxy/pf_context.c new file mode 100644 index 0000000..04433d9 --- /dev/null +++ b/server/proxy/pf_context.c @@ -0,0 +1,394 @@ +/** + * 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 <winpr/crypto.h> +#include <winpr/print.h> + +#include <freerdp/server/proxy/proxy_log.h> +#include <freerdp/server/proxy/proxy_server.h> + +#include "pf_client.h" +#include "pf_utils.h" +#include "proxy_modules.h" + +#include <freerdp/server/proxy/proxy_context.h> + +#include "channels/pf_channel_rdpdr.h" + +#define TAG PROXY_TAG("server") + +static UINT32 ChannelId_Hash(const void* key) +{ + const UINT32* v = (const UINT32*)key; + return *v; +} + +static BOOL ChannelId_Compare(const void* pv1, const void* pv2) +{ + const UINT32* v1 = pv1; + const UINT32* v2 = pv2; + WINPR_ASSERT(v1); + WINPR_ASSERT(v2); + return (*v1 == *v2); +} + +pServerStaticChannelContext* StaticChannelContext_new(pServerContext* ps, const char* name, + UINT32 id) +{ + pServerStaticChannelContext* ret = calloc(1, sizeof(*ret)); + if (!ret) + { + PROXY_LOG_ERR(TAG, ps, "error allocating channel context for '%s'", name); + return NULL; + } + + ret->front_channel_id = id; + ret->channel_name = _strdup(name); + if (!ret->channel_name) + { + PROXY_LOG_ERR(TAG, ps, "error allocating name in channel context for '%s'", name); + free(ret); + return NULL; + } + + proxyChannelToInterceptData channel = { .name = name, .channelId = id, .intercept = FALSE }; + + if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_STATIC_INTERCEPT_LIST, ps->pdata, + &channel) && + channel.intercept) + ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT; + else + ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name); + return ret; +} + +void StaticChannelContext_free(pServerStaticChannelContext* ctx) +{ + if (!ctx) + return; + + IFCALL(ctx->contextDtor, ctx->context); + + free(ctx->channel_name); + free(ctx); +} + +static void HashStaticChannelContext_free(void* ptr) +{ + pServerStaticChannelContext* ctx = (pServerStaticChannelContext*)ptr; + StaticChannelContext_free(ctx); +} + +/* Proxy context initialization callback */ +static void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx); +static BOOL client_to_proxy_context_new(freerdp_peer* client, rdpContext* ctx) +{ + wObject* obj = NULL; + pServerContext* context = (pServerContext*)ctx; + + WINPR_ASSERT(client); + WINPR_ASSERT(context); + + context->dynvcReady = NULL; + + context->vcm = WTSOpenServerA((LPSTR)client->context); + + if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE) + goto error; + + if (!(context->dynvcReady = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto error; + + context->interceptContextMap = HashTable_New(FALSE); + if (!context->interceptContextMap) + goto error; + if (!HashTable_SetupForStringData(context->interceptContextMap, FALSE)) + goto error; + obj = HashTable_ValueObject(context->interceptContextMap); + WINPR_ASSERT(obj); + obj->fnObjectFree = intercept_context_entry_free; + + /* channels by ids */ + context->channelsByFrontId = HashTable_New(FALSE); + if (!context->channelsByFrontId) + goto error; + if (!HashTable_SetHashFunction(context->channelsByFrontId, ChannelId_Hash)) + goto error; + + obj = HashTable_KeyObject(context->channelsByFrontId); + obj->fnObjectEquals = ChannelId_Compare; + + obj = HashTable_ValueObject(context->channelsByFrontId); + obj->fnObjectFree = HashStaticChannelContext_free; + + context->channelsByBackId = HashTable_New(FALSE); + if (!context->channelsByBackId) + goto error; + if (!HashTable_SetHashFunction(context->channelsByBackId, ChannelId_Hash)) + goto error; + + obj = HashTable_KeyObject(context->channelsByBackId); + obj->fnObjectEquals = ChannelId_Compare; + + return TRUE; + +error: + client_to_proxy_context_free(client, ctx); + + return FALSE; +} + +/* Proxy context free callback */ +void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx) +{ + pServerContext* context = (pServerContext*)ctx; + + WINPR_UNUSED(client); + + if (!context) + return; + + if (context->dynvcReady) + { + CloseHandle(context->dynvcReady); + context->dynvcReady = NULL; + } + + HashTable_Free(context->interceptContextMap); + HashTable_Free(context->channelsByFrontId); + HashTable_Free(context->channelsByBackId); + + if (context->vcm && (context->vcm != INVALID_HANDLE_VALUE)) + WTSCloseServer((HANDLE)context->vcm); + context->vcm = NULL; +} + +BOOL pf_context_init_server_context(freerdp_peer* client) +{ + WINPR_ASSERT(client); + + client->ContextSize = sizeof(pServerContext); + client->ContextNew = client_to_proxy_context_new; + client->ContextFree = client_to_proxy_context_free; + + return freerdp_peer_context_new(client); +} + +static BOOL pf_context_revert_str_settings(rdpSettings* dst, const rdpSettings* before, size_t nr, + const FreeRDP_Settings_Keys_String* ids) +{ + WINPR_ASSERT(dst); + WINPR_ASSERT(before); + WINPR_ASSERT(ids || (nr == 0)); + + for (size_t x = 0; x < nr; x++) + { + FreeRDP_Settings_Keys_String id = ids[x]; + const char* what = freerdp_settings_get_string(before, id); + if (!freerdp_settings_set_string(dst, id, what)) + return FALSE; + } + + return TRUE; +} + +void intercept_context_entry_free(void* obj) +{ + InterceptContextMapEntry* entry = obj; + if (!entry) + return; + if (!entry->free) + return; + entry->free(entry); +} + +BOOL pf_context_copy_settings(rdpSettings* dst, const rdpSettings* src) +{ + BOOL rc = FALSE; + rdpSettings* before_copy = NULL; + const FreeRDP_Settings_Keys_String to_revert[] = { FreeRDP_ConfigPath, + FreeRDP_CertificateName }; + + if (!dst || !src) + return FALSE; + + before_copy = freerdp_settings_clone(dst); + if (!before_copy) + return FALSE; + + if (!freerdp_settings_copy(dst, src)) + goto out_fail; + + /* keep original ServerMode value */ + if (!freerdp_settings_copy_item(dst, before_copy, FreeRDP_ServerMode)) + goto out_fail; + + /* revert some values that must not be changed */ + if (!pf_context_revert_str_settings(dst, before_copy, ARRAYSIZE(to_revert), to_revert)) + goto out_fail; + + if (!freerdp_settings_get_bool(dst, FreeRDP_ServerMode)) + { + /* adjust instance pointer */ + if (!freerdp_settings_copy_item(dst, before_copy, FreeRDP_instance)) + goto out_fail; + + /* + * RdpServerRsaKey must be set to NULL if `dst` is client's context + * it must be freed before setting it to NULL to avoid a memory leak! + */ + + if (!freerdp_settings_set_pointer_len(dst, FreeRDP_RdpServerRsaKey, NULL, 1)) + goto out_fail; + } + + /* We handle certificate management for this client ourselfes. */ + rc = freerdp_settings_set_bool(dst, FreeRDP_ExternalCertificateManagement, TRUE); + +out_fail: + freerdp_settings_free(before_copy); + return rc; +} + +pClientContext* pf_context_create_client_context(const rdpSettings* clientSettings) +{ + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + pClientContext* pc = NULL; + rdpContext* context = NULL; + + WINPR_ASSERT(clientSettings); + + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); + + if (!context) + return NULL; + + pc = (pClientContext*)context; + + if (!pf_context_copy_settings(context->settings, clientSettings)) + goto error; + + return pc; +error: + freerdp_client_context_free(context); + return NULL; +} + +proxyData* proxy_data_new(void) +{ + BYTE temp[16]; + char* hex = NULL; + proxyData* pdata = NULL; + + pdata = calloc(1, sizeof(proxyData)); + if (!pdata) + return NULL; + + if (!(pdata->abort_event = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto error; + + if (!(pdata->gfx_server_ready = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto error; + + winpr_RAND(&temp, 16); + hex = winpr_BinToHexString(temp, 16, FALSE); + if (!hex) + goto error; + + CopyMemory(pdata->session_id, hex, PROXY_SESSION_ID_LENGTH); + pdata->session_id[PROXY_SESSION_ID_LENGTH] = '\0'; + free(hex); + + if (!(pdata->modules_info = HashTable_New(FALSE))) + goto error; + + /* modules_info maps between plugin name to custom data */ + if (!HashTable_SetupForStringData(pdata->modules_info, FALSE)) + goto error; + + return pdata; +error: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + proxy_data_free(pdata); + WINPR_PRAGMA_DIAG_POP + return NULL; +} + +/* updates circular pointers between proxyData and pClientContext instances */ +void proxy_data_set_client_context(proxyData* pdata, pClientContext* context) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(context); + pdata->pc = context; + context->pdata = pdata; +} + +/* updates circular pointers between proxyData and pServerContext instances */ +void proxy_data_set_server_context(proxyData* pdata, pServerContext* context) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(context); + pdata->ps = context; + context->pdata = pdata; +} + +void proxy_data_free(proxyData* pdata) +{ + if (!pdata) + return; + + if (pdata->abort_event) + CloseHandle(pdata->abort_event); + + if (pdata->client_thread) + CloseHandle(pdata->client_thread); + + if (pdata->gfx_server_ready) + CloseHandle(pdata->gfx_server_ready); + + if (pdata->modules_info) + HashTable_Free(pdata->modules_info); + + if (pdata->pc) + freerdp_client_context_free(&pdata->pc->context); + + free(pdata); +} + +void proxy_data_abort_connect(proxyData* pdata) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(pdata->abort_event); + SetEvent(pdata->abort_event); + if (pdata->pc) + freerdp_abort_connect_context(&pdata->pc->context); +} + +BOOL proxy_data_shall_disconnect(proxyData* pdata) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(pdata->abort_event); + return WaitForSingleObject(pdata->abort_event, 0) == WAIT_OBJECT_0; +} diff --git a/server/proxy/pf_input.c b/server/proxy/pf_input.c new file mode 100644 index 0000000..2ed1c14 --- /dev/null +++ b/server/proxy/pf_input.c @@ -0,0 +1,206 @@ +/** + * 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 <winpr/assert.h> + +#include "pf_input.h" +#include <freerdp/server/proxy/proxy_config.h> +#include <freerdp/server/proxy/proxy_context.h> + +#include "proxy_modules.h" + +static BOOL pf_server_check_and_sync_input_state(pClientContext* pc) +{ + WINPR_ASSERT(pc); + + if (!freerdp_is_active_state(&pc->context)) + return FALSE; + if (pc->input_state_sync_pending) + { + BOOL rc = freerdp_input_send_synchronize_event(pc->context.input, pc->input_state); + if (rc) + pc->input_state_sync_pending = FALSE; + } + return TRUE; +} + +static BOOL pf_server_synchronize_event(rdpInput* input, UINT32 flags) +{ + pServerContext* ps = NULL; + pClientContext* pc = NULL; + + WINPR_ASSERT(input); + ps = (pServerContext*)input->context; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + + pc = ps->pdata->pc; + WINPR_ASSERT(pc); + + pc->input_state = flags; + pc->input_state_sync_pending = TRUE; + + pf_server_check_and_sync_input_state(pc); + return TRUE; +} + +static BOOL pf_server_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code) +{ + const proxyConfig* config = NULL; + proxyKeyboardEventInfo event = { 0 }; + pServerContext* ps = NULL; + pClientContext* pc = NULL; + + WINPR_ASSERT(input); + ps = (pServerContext*)input->context; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + + pc = ps->pdata->pc; + WINPR_ASSERT(pc); + + config = ps->pdata->config; + WINPR_ASSERT(config); + + if (!pf_server_check_and_sync_input_state(pc)) + return TRUE; + + if (!config->Keyboard) + return TRUE; + + event.flags = flags; + event.rdp_scan_code = code; + + if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_KEYBOARD, pc->pdata, &event)) + return freerdp_input_send_keyboard_event(pc->context.input, flags, code); + + return TRUE; +} + +static BOOL pf_server_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) +{ + const proxyConfig* config = NULL; + proxyUnicodeEventInfo event = { 0 }; + pServerContext* ps = NULL; + pClientContext* pc = NULL; + + WINPR_ASSERT(input); + ps = (pServerContext*)input->context; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + + pc = ps->pdata->pc; + WINPR_ASSERT(pc); + + config = ps->pdata->config; + WINPR_ASSERT(config); + + if (!pf_server_check_and_sync_input_state(pc)) + return TRUE; + + if (!config->Keyboard) + return TRUE; + + event.flags = flags; + event.code = code; + if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_UNICODE, pc->pdata, &event)) + return freerdp_input_send_unicode_keyboard_event(pc->context.input, flags, code); + return TRUE; +} + +static BOOL pf_server_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + proxyMouseEventInfo event = { 0 }; + const proxyConfig* config = NULL; + pServerContext* ps = NULL; + pClientContext* pc = NULL; + + WINPR_ASSERT(input); + ps = (pServerContext*)input->context; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + + pc = ps->pdata->pc; + WINPR_ASSERT(pc); + + config = ps->pdata->config; + WINPR_ASSERT(config); + + if (!pf_server_check_and_sync_input_state(pc)) + return TRUE; + + if (!config->Mouse) + return TRUE; + + event.flags = flags; + event.x = x; + event.y = y; + + if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_MOUSE, pc->pdata, &event)) + return freerdp_input_send_mouse_event(pc->context.input, flags, x, y); + + return TRUE; +} + +static BOOL pf_server_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + const proxyConfig* config = NULL; + proxyMouseExEventInfo event = { 0 }; + pServerContext* ps = NULL; + pClientContext* pc = NULL; + + WINPR_ASSERT(input); + ps = (pServerContext*)input->context; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + + pc = ps->pdata->pc; + WINPR_ASSERT(pc); + + config = ps->pdata->config; + WINPR_ASSERT(config); + + if (!pf_server_check_and_sync_input_state(pc)) + return TRUE; + + if (!config->Mouse) + return TRUE; + + event.flags = flags; + event.x = x; + event.y = y; + if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_MOUSE, pc->pdata, &event)) + return freerdp_input_send_extended_mouse_event(pc->context.input, flags, x, y); + return TRUE; +} + +void pf_server_register_input_callbacks(rdpInput* input) +{ + WINPR_ASSERT(input); + + input->SynchronizeEvent = pf_server_synchronize_event; + input->KeyboardEvent = pf_server_keyboard_event; + input->UnicodeKeyboardEvent = pf_server_unicode_keyboard_event; + input->MouseEvent = pf_server_mouse_event; + input->ExtendedMouseEvent = pf_server_extended_mouse_event; +} diff --git a/server/proxy/pf_input.h b/server/proxy/pf_input.h new file mode 100644 index 0000000..ea9c542 --- /dev/null +++ b/server/proxy/pf_input.h @@ -0,0 +1,29 @@ +/** + * 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> + * + * 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. + */ + +#ifndef FREERDP_SERVER_PROXY_PFINPUT_H +#define FREERDP_SERVER_PROXY_PFINPUT_H + +#include <freerdp/freerdp.h> + +void pf_server_register_input_callbacks(rdpInput* input); + +#endif /* FREERDP_SERVER_PROXY_PFINPUT_H */ diff --git a/server/proxy/pf_modules.c b/server/proxy/pf_modules.c new file mode 100644 index 0000000..e3447cb --- /dev/null +++ b/server/proxy/pf_modules.c @@ -0,0 +1,633 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server modules API + * + * 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 <winpr/assert.h> + +#include <winpr/file.h> +#include <winpr/wlog.h> +#include <winpr/path.h> +#include <winpr/library.h> +#include <freerdp/api.h> +#include <freerdp/build-config.h> + +#include <freerdp/server/proxy/proxy_log.h> +#include <freerdp/server/proxy/proxy_modules_api.h> + +#include <freerdp/server/proxy/proxy_context.h> +#include "proxy_modules.h" + +#define TAG PROXY_TAG("modules") + +#define MODULE_ENTRY_POINT "proxy_module_entry_point" + +struct proxy_module +{ + proxyPluginsManager mgr; + wArrayList* plugins; + wArrayList* handles; +}; + +static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result) +{ + switch (result) + { + case FILTER_TYPE_KEYBOARD: + return "FILTER_TYPE_KEYBOARD"; + case FILTER_TYPE_UNICODE: + return "FILTER_TYPE_UNICODE"; + case FILTER_TYPE_MOUSE: + return "FILTER_TYPE_MOUSE"; + case FILTER_TYPE_MOUSE_EX: + return "FILTER_TYPE_MOUSE_EX"; + case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA: + return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA"; + case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA: + return "FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA"; + case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE: + return "FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE"; + case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR: + return "FILTER_TYPE_SERVER_FETCH_TARGET_ADDR"; + case FILTER_TYPE_SERVER_PEER_LOGON: + return "FILTER_TYPE_SERVER_PEER_LOGON"; + case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE: + return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE"; + case FILTER_LAST: + return "FILTER_LAST"; + default: + return "FILTER_UNKNOWN"; + } +} + +static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result) +{ + switch (result) + { + case HOOK_TYPE_CLIENT_INIT_CONNECT: + return "HOOK_TYPE_CLIENT_INIT_CONNECT"; + case HOOK_TYPE_CLIENT_UNINIT_CONNECT: + return "HOOK_TYPE_CLIENT_UNINIT_CONNECT"; + case HOOK_TYPE_CLIENT_PRE_CONNECT: + return "HOOK_TYPE_CLIENT_PRE_CONNECT"; + case HOOK_TYPE_CLIENT_POST_CONNECT: + return "HOOK_TYPE_CLIENT_POST_CONNECT"; + case HOOK_TYPE_CLIENT_POST_DISCONNECT: + return "HOOK_TYPE_CLIENT_POST_DISCONNECT"; + case HOOK_TYPE_CLIENT_REDIRECT: + return "HOOK_TYPE_CLIENT_REDIRECT"; + case HOOK_TYPE_CLIENT_VERIFY_X509: + return "HOOK_TYPE_CLIENT_VERIFY_X509"; + case HOOK_TYPE_CLIENT_LOGIN_FAILURE: + return "HOOK_TYPE_CLIENT_LOGIN_FAILURE"; + case HOOK_TYPE_CLIENT_END_PAINT: + return "HOOK_TYPE_CLIENT_END_PAINT"; + case HOOK_TYPE_SERVER_POST_CONNECT: + return "HOOK_TYPE_SERVER_POST_CONNECT"; + case HOOK_TYPE_SERVER_ACTIVATE: + return "HOOK_TYPE_SERVER_ACTIVATE"; + case HOOK_TYPE_SERVER_CHANNELS_INIT: + return "HOOK_TYPE_SERVER_CHANNELS_INIT"; + case HOOK_TYPE_SERVER_CHANNELS_FREE: + return "HOOK_TYPE_SERVER_CHANNELS_FREE"; + case HOOK_TYPE_SERVER_SESSION_END: + return "HOOK_TYPE_SERVER_SESSION_END"; + case HOOK_TYPE_CLIENT_LOAD_CHANNELS: + return "HOOK_TYPE_CLIENT_LOAD_CHANNELS"; + case HOOK_TYPE_SERVER_SESSION_INITIALIZE: + return "HOOK_TYPE_SERVER_SESSION_INITIALIZE"; + case HOOK_TYPE_SERVER_SESSION_STARTED: + return "HOOK_TYPE_SERVER_SESSION_STARTED"; + case HOOK_LAST: + return "HOOK_LAST"; + default: + return "HOOK_TYPE_UNKNOWN"; + } +} + +static BOOL pf_modules_proxy_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) +{ + proxyPlugin* plugin = (proxyPlugin*)data; + BOOL ok = FALSE; + + WINPR_UNUSED(index); + + PF_HOOK_TYPE type = va_arg(ap, PF_HOOK_TYPE); + proxyData* pdata = va_arg(ap, proxyData*); + void* custom = va_arg(ap, void*); + + WLog_VRB(TAG, "running hook %s.%s", plugin->name, pf_modules_get_hook_type_string(type)); + + switch (type) + { + case HOOK_TYPE_CLIENT_INIT_CONNECT: + ok = IFCALLRESULT(TRUE, plugin->ClientInitConnect, plugin, pdata, custom); + break; + case HOOK_TYPE_CLIENT_UNINIT_CONNECT: + ok = IFCALLRESULT(TRUE, plugin->ClientUninitConnect, plugin, pdata, custom); + break; + case HOOK_TYPE_CLIENT_PRE_CONNECT: + ok = IFCALLRESULT(TRUE, plugin->ClientPreConnect, plugin, pdata, custom); + break; + + case HOOK_TYPE_CLIENT_POST_CONNECT: + ok = IFCALLRESULT(TRUE, plugin->ClientPostConnect, plugin, pdata, custom); + break; + + case HOOK_TYPE_CLIENT_REDIRECT: + ok = IFCALLRESULT(TRUE, plugin->ClientRedirect, plugin, pdata, custom); + break; + + case HOOK_TYPE_CLIENT_POST_DISCONNECT: + ok = IFCALLRESULT(TRUE, plugin->ClientPostDisconnect, plugin, pdata, custom); + break; + + case HOOK_TYPE_CLIENT_VERIFY_X509: + ok = IFCALLRESULT(TRUE, plugin->ClientX509Certificate, plugin, pdata, custom); + break; + + case HOOK_TYPE_CLIENT_LOGIN_FAILURE: + ok = IFCALLRESULT(TRUE, plugin->ClientLoginFailure, plugin, pdata, custom); + break; + + case HOOK_TYPE_CLIENT_END_PAINT: + ok = IFCALLRESULT(TRUE, plugin->ClientEndPaint, plugin, pdata, custom); + break; + + case HOOK_TYPE_CLIENT_LOAD_CHANNELS: + ok = IFCALLRESULT(TRUE, plugin->ClientLoadChannels, plugin, pdata, custom); + break; + + case HOOK_TYPE_SERVER_POST_CONNECT: + ok = IFCALLRESULT(TRUE, plugin->ServerPostConnect, plugin, pdata, custom); + break; + + case HOOK_TYPE_SERVER_ACTIVATE: + ok = IFCALLRESULT(TRUE, plugin->ServerPeerActivate, plugin, pdata, custom); + break; + + case HOOK_TYPE_SERVER_CHANNELS_INIT: + ok = IFCALLRESULT(TRUE, plugin->ServerChannelsInit, plugin, pdata, custom); + break; + + case HOOK_TYPE_SERVER_CHANNELS_FREE: + ok = IFCALLRESULT(TRUE, plugin->ServerChannelsFree, plugin, pdata, custom); + break; + + case HOOK_TYPE_SERVER_SESSION_END: + ok = IFCALLRESULT(TRUE, plugin->ServerSessionEnd, plugin, pdata, custom); + break; + + case HOOK_TYPE_SERVER_SESSION_INITIALIZE: + ok = IFCALLRESULT(TRUE, plugin->ServerSessionInitialize, plugin, pdata, custom); + break; + + case HOOK_TYPE_SERVER_SESSION_STARTED: + ok = IFCALLRESULT(TRUE, plugin->ServerSessionStarted, plugin, pdata, custom); + break; + + case HOOK_LAST: + default: + WLog_ERR(TAG, "invalid hook called"); + } + + if (!ok) + { + WLog_INFO(TAG, "plugin %s, hook %s failed!", plugin->name, + pf_modules_get_hook_type_string(type)); + return FALSE; + } + return TRUE; +} + +/* + * runs all hooks of type `type`. + * + * @type: hook type to run. + * @server: pointer of server's rdpContext struct of the current session. + */ +BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata, void* custom) +{ + WINPR_ASSERT(module); + WINPR_ASSERT(module->plugins); + return ArrayList_ForEach(module->plugins, pf_modules_proxy_ArrayList_ForEachFkt, type, pdata, + custom); +} + +static BOOL pf_modules_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) +{ + proxyPlugin* plugin = (proxyPlugin*)data; + BOOL result = FALSE; + + WINPR_UNUSED(index); + + PF_FILTER_TYPE type = va_arg(ap, PF_FILTER_TYPE); + proxyData* pdata = va_arg(ap, proxyData*); + void* param = va_arg(ap, void*); + + WLog_VRB(TAG, "running filter: %s", plugin->name); + + switch (type) + { + case FILTER_TYPE_KEYBOARD: + result = IFCALLRESULT(TRUE, plugin->KeyboardEvent, plugin, pdata, param); + break; + + case FILTER_TYPE_UNICODE: + result = IFCALLRESULT(TRUE, plugin->UnicodeEvent, plugin, pdata, param); + break; + + case FILTER_TYPE_MOUSE: + result = IFCALLRESULT(TRUE, plugin->MouseEvent, plugin, pdata, param); + break; + + case FILTER_TYPE_MOUSE_EX: + result = IFCALLRESULT(TRUE, plugin->MouseExEvent, plugin, pdata, param); + break; + + case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA: + result = IFCALLRESULT(TRUE, plugin->ClientChannelData, plugin, pdata, param); + break; + + case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA: + result = IFCALLRESULT(TRUE, plugin->ServerChannelData, plugin, pdata, param); + break; + + case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE: + result = IFCALLRESULT(TRUE, plugin->ChannelCreate, plugin, pdata, param); + break; + + case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE: + result = IFCALLRESULT(TRUE, plugin->DynamicChannelCreate, plugin, pdata, param); + break; + + case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR: + result = IFCALLRESULT(TRUE, plugin->ServerFetchTargetAddr, plugin, pdata, param); + break; + + case FILTER_TYPE_SERVER_PEER_LOGON: + result = IFCALLRESULT(TRUE, plugin->ServerPeerLogon, plugin, pdata, param); + break; + + case FILTER_TYPE_INTERCEPT_CHANNEL: + result = IFCALLRESULT(TRUE, plugin->DynChannelIntercept, plugin, pdata, param); + break; + + case FILTER_TYPE_DYN_INTERCEPT_LIST: + result = IFCALLRESULT(TRUE, plugin->DynChannelToIntercept, plugin, pdata, param); + break; + + case FILTER_TYPE_STATIC_INTERCEPT_LIST: + result = IFCALLRESULT(TRUE, plugin->StaticChannelToIntercept, plugin, pdata, param); + break; + + case FILTER_LAST: + default: + WLog_ERR(TAG, "invalid filter called"); + } + + if (!result) + { + /* current filter return FALSE, no need to run other filters. */ + WLog_DBG(TAG, "plugin %s, filter type [%s] returned FALSE", plugin->name, + pf_modules_get_filter_type_string(type)); + } + return result; +} + +/* + * runs all filters of type `type`. + * + * @type: filter type to run. + * @server: pointer of server's rdpContext struct of the current session. + */ +BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata, void* param) +{ + WINPR_ASSERT(module); + WINPR_ASSERT(module->plugins); + + return ArrayList_ForEach(module->plugins, pf_modules_ArrayList_ForEachFkt, type, pdata, param); +} + +/* + * stores per-session data needed by a plugin. + * + * @context: current session server's rdpContext instance. + * @info: pointer to per-session data. + */ +static BOOL pf_modules_set_plugin_data(proxyPluginsManager* mgr, const char* plugin_name, + proxyData* pdata, void* data) +{ + union + { + const char* ccp; + char* cp; + } ccharconv; + + WINPR_ASSERT(plugin_name); + + ccharconv.ccp = plugin_name; + if (data == NULL) /* no need to store anything */ + return FALSE; + + if (!HashTable_Insert(pdata->modules_info, ccharconv.cp, data)) + { + WLog_ERR(TAG, "[%s]: HashTable_Insert failed!"); + return FALSE; + } + + return TRUE; +} + +/* + * returns per-session data needed a plugin. + * + * @context: current session server's rdpContext instance. + * if there's no data related to `plugin_name` in `context` (current session), a NULL will be + * returned. + */ +static void* pf_modules_get_plugin_data(proxyPluginsManager* mgr, const char* plugin_name, + proxyData* pdata) +{ + union + { + const char* ccp; + char* cp; + } ccharconv; + WINPR_ASSERT(plugin_name); + WINPR_ASSERT(pdata); + ccharconv.ccp = plugin_name; + + return HashTable_GetItemValue(pdata->modules_info, ccharconv.cp); +} + +static void pf_modules_abort_connect(proxyPluginsManager* mgr, proxyData* pdata) +{ + WINPR_ASSERT(pdata); + WLog_DBG(TAG, "is called!"); + proxy_data_abort_connect(pdata); +} + +static BOOL pf_modules_register_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) +{ + proxyPlugin* plugin = (proxyPlugin*)data; + proxyPlugin* plugin_to_register = va_arg(ap, proxyPlugin*); + + WINPR_UNUSED(index); + + if (strcmp(plugin->name, plugin_to_register->name) == 0) + { + WLog_ERR(TAG, "can not register plugin '%s', it is already registered!", plugin->name); + return FALSE; + } + return TRUE; +} + +static BOOL pf_modules_register_plugin(proxyPluginsManager* mgr, + const proxyPlugin* plugin_to_register) +{ + proxyPlugin internal = { 0 }; + proxyModule* module = (proxyModule*)mgr; + WINPR_ASSERT(module); + + if (!plugin_to_register) + return FALSE; + + internal = *plugin_to_register; + internal.mgr = mgr; + + /* make sure there's no other loaded plugin with the same name of `plugin_to_register`. */ + if (!ArrayList_ForEach(module->plugins, pf_modules_register_ArrayList_ForEachFkt, &internal)) + return FALSE; + + if (!ArrayList_Append(module->plugins, &internal)) + { + WLog_ERR(TAG, "failed adding plugin to list: %s", plugin_to_register->name); + return FALSE; + } + + return TRUE; +} + +static BOOL pf_modules_load_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) +{ + proxyPlugin* plugin = (proxyPlugin*)data; + const char* plugin_name = va_arg(ap, const char*); + BOOL* res = va_arg(ap, BOOL*); + + WINPR_UNUSED(index); + WINPR_UNUSED(ap); + WINPR_ASSERT(res); + + if (strcmp(plugin->name, plugin_name) == 0) + *res = TRUE; + return TRUE; +} + +BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name) +{ + BOOL rc = FALSE; + WINPR_ASSERT(module); + if (ArrayList_Count(module->plugins) < 1) + return FALSE; + if (!ArrayList_ForEach(module->plugins, pf_modules_load_ArrayList_ForEachFkt, plugin_name, &rc)) + return FALSE; + return rc; +} + +static BOOL pf_modules_print_ArrayList_ForEachFkt(void* data, size_t index, va_list ap) +{ + proxyPlugin* plugin = (proxyPlugin*)data; + + WINPR_UNUSED(index); + WINPR_UNUSED(ap); + + WLog_INFO(TAG, "\tName: %s", plugin->name); + WLog_INFO(TAG, "\tDescription: %s", plugin->description); + return TRUE; +} + +void pf_modules_list_loaded_plugins(proxyModule* module) +{ + size_t count = 0; + + WINPR_ASSERT(module); + WINPR_ASSERT(module->plugins); + + count = ArrayList_Count(module->plugins); + + if (count > 0) + WLog_INFO(TAG, "Loaded plugins:"); + + ArrayList_ForEach(module->plugins, pf_modules_print_ArrayList_ForEachFkt); +} + +static BOOL pf_modules_load_module(const char* module_path, proxyModule* module, void* userdata) +{ + HMODULE handle = NULL; + proxyModuleEntryPoint pEntryPoint = NULL; + WINPR_ASSERT(module); + + handle = LoadLibraryX(module_path); + + if (handle == NULL) + { + WLog_ERR(TAG, "failed loading external library: %s", module_path); + return FALSE; + } + + pEntryPoint = (proxyModuleEntryPoint)GetProcAddress(handle, MODULE_ENTRY_POINT); + if (!pEntryPoint) + { + WLog_ERR(TAG, "GetProcAddress failed while loading %s", module_path); + goto error; + } + if (!ArrayList_Append(module->handles, handle)) + { + WLog_ERR(TAG, "ArrayList_Append failed!"); + goto error; + } + return pf_modules_add(module, pEntryPoint, userdata); + +error: + FreeLibrary(handle); + return FALSE; +} + +static void free_handle(void* obj) +{ + HANDLE handle = (HANDLE)obj; + if (handle) + FreeLibrary(handle); +} + +static void free_plugin(void* obj) +{ + proxyPlugin* plugin = (proxyPlugin*)obj; + WINPR_ASSERT(plugin); + + if (!IFCALLRESULT(TRUE, plugin->PluginUnload, plugin)) + WLog_WARN(TAG, "PluginUnload failed for plugin '%s'", plugin->name); + + free(plugin); +} + +static void* new_plugin(const void* obj) +{ + const proxyPlugin* src = obj; + proxyPlugin* proxy = calloc(1, sizeof(proxyPlugin)); + if (!proxy) + return NULL; + *proxy = *src; + return proxy; +} + +proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count) +{ + wObject* obj = NULL; + char* path = NULL; + proxyModule* module = calloc(1, sizeof(proxyModule)); + if (!module) + return NULL; + + module->mgr.RegisterPlugin = pf_modules_register_plugin; + module->mgr.SetPluginData = pf_modules_set_plugin_data; + module->mgr.GetPluginData = pf_modules_get_plugin_data; + module->mgr.AbortConnect = pf_modules_abort_connect; + module->plugins = ArrayList_New(FALSE); + + if (module->plugins == NULL) + { + WLog_ERR(TAG, "ArrayList_New failed!"); + goto error; + } + obj = ArrayList_Object(module->plugins); + WINPR_ASSERT(obj); + + obj->fnObjectFree = free_plugin; + obj->fnObjectNew = new_plugin; + + module->handles = ArrayList_New(FALSE); + if (module->handles == NULL) + { + + WLog_ERR(TAG, "ArrayList_New failed!"); + goto error; + } + ArrayList_Object(module->handles)->fnObjectFree = free_handle; + + if (count > 0) + { + WINPR_ASSERT(root_dir); + if (!winpr_PathFileExists(root_dir)) + path = GetCombinedPath(FREERDP_INSTALL_PREFIX, root_dir); + else + path = _strdup(root_dir); + + if (!winpr_PathFileExists(path)) + { + if (!winpr_PathMakePath(path, NULL)) + { + WLog_ERR(TAG, "error occurred while creating modules directory: %s", root_dir); + goto error; + } + } + + if (winpr_PathFileExists(path)) + WLog_DBG(TAG, "modules root directory: %s", path); + + for (size_t i = 0; i < count; i++) + { + char name[8192] = { 0 }; + char* fullpath = NULL; + _snprintf(name, sizeof(name), "proxy-%s-plugin%s", modules[i], + FREERDP_SHARED_LIBRARY_SUFFIX); + fullpath = GetCombinedPath(path, name); + pf_modules_load_module(fullpath, module, NULL); + free(fullpath); + } + } + + free(path); + return module; + +error: + free(path); + pf_modules_free(module); + return NULL; +} + +void pf_modules_free(proxyModule* module) +{ + if (!module) + return; + + ArrayList_Free(module->plugins); + ArrayList_Free(module->handles); + free(module); +} + +BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata) +{ + WINPR_ASSERT(module); + WINPR_ASSERT(ep); + + return ep(&module->mgr, userdata); +} 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); +} diff --git a/server/proxy/pf_server.h b/server/proxy/pf_server.h new file mode 100644 index 0000000..2ac84f2 --- /dev/null +++ b/server/proxy/pf_server.h @@ -0,0 +1,43 @@ +/** + * 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. + */ + +#ifndef INT_FREERDP_SERVER_PROXY_SERVER_H +#define INT_FREERDP_SERVER_PROXY_SERVER_H + +#include <winpr/collections.h> +#include <freerdp/listener.h> + +#include <freerdp/server/proxy/proxy_config.h> +#include "proxy_modules.h" + +struct proxy_server +{ + proxyModule* module; + proxyConfig* config; + + freerdp_listener* listener; + HANDLE stopEvent; /* an event used to signal the main thread to stop */ + wArrayList* peer_list; +}; + +#endif /* INT_FREERDP_SERVER_PROXY_SERVER_H */ diff --git a/server/proxy/pf_update.c b/server/proxy/pf_update.c new file mode 100644 index 0000000..e572810 --- /dev/null +++ b/server/proxy/pf_update.c @@ -0,0 +1,629 @@ +/** + * 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/display.h> +#include <freerdp/session.h> +#include <winpr/assert.h> +#include <winpr/image.h> +#include <winpr/sysinfo.h> + +#include <freerdp/server/proxy/proxy_log.h> + +#include "pf_update.h" +#include <freerdp/server/proxy/proxy_context.h> +#include "proxy_modules.h" + +#define TAG PROXY_TAG("update") + +static BOOL pf_server_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas) +{ + pServerContext* ps = (pServerContext*)context; + rdpContext* pc = NULL; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + pc = (rdpContext*)ps->pdata->pc; + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->update); + WINPR_ASSERT(pc->update->RefreshRect); + return pc->update->RefreshRect(pc, count, areas); +} + +static BOOL pf_server_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area) +{ + pServerContext* ps = (pServerContext*)context; + rdpContext* pc = NULL; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->pdata); + pc = (rdpContext*)ps->pdata->pc; + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->update); + WINPR_ASSERT(pc->update->SuppressOutput); + return pc->update->SuppressOutput(pc, allow, area); +} + +/* Proxy from PC to PS */ + +/** + * This function is called whenever a new frame starts. + * It can be used to reset invalidated areas. + */ +static BOOL pf_client_begin_paint(rdpContext* context) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->BeginPaint); + WLog_DBG(TAG, "called"); + return ps->update->BeginPaint(ps); +} + +/** + * This function is called when the library completed composing a new + * frame. Read out the changed areas and blit them to your output device. + * The image buffer will have the format specified by gdi_init + */ +static BOOL pf_client_end_paint(rdpContext* context) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->EndPaint); + + WLog_DBG(TAG, "called"); + + /* proxy end paint */ + if (!ps->update->EndPaint(ps)) + return FALSE; + + if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_END_PAINT, pdata, context)) + return FALSE; + + return TRUE; +} + +static BOOL pf_client_bitmap_update(rdpContext* context, const BITMAP_UPDATE* bitmap) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->BitmapUpdate); + WLog_DBG(TAG, "called"); + return ps->update->BitmapUpdate(ps, bitmap); +} + +static BOOL pf_client_desktop_resize(rdpContext* context) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->DesktopResize); + WINPR_ASSERT(context->settings); + WINPR_ASSERT(ps->settings); + WLog_DBG(TAG, "called"); + if (!freerdp_settings_copy_item(ps->settings, context->settings, FreeRDP_DesktopWidth)) + return FALSE; + if (!freerdp_settings_copy_item(ps->settings, context->settings, FreeRDP_DesktopHeight)) + return FALSE; + return ps->update->DesktopResize(ps); +} + +static BOOL pf_client_remote_monitors(rdpContext* context, UINT32 count, + const MONITOR_DEF* monitors) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WLog_DBG(TAG, "called"); + return freerdp_display_send_monitor_layout(ps, count, monitors); +} + +static BOOL pf_client_send_pointer_system(rdpContext* context, + const POINTER_SYSTEM_UPDATE* pointer_system) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->pointer); + WINPR_ASSERT(ps->update->pointer->PointerSystem); + WLog_DBG(TAG, "called"); + return ps->update->pointer->PointerSystem(ps, pointer_system); +} + +static BOOL pf_client_send_pointer_position(rdpContext* context, + const POINTER_POSITION_UPDATE* pointerPosition) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->pointer); + WINPR_ASSERT(ps->update->pointer->PointerPosition); + WLog_DBG(TAG, "called"); + return ps->update->pointer->PointerPosition(ps, pointerPosition); +} + +static BOOL pf_client_send_pointer_color(rdpContext* context, + const POINTER_COLOR_UPDATE* pointer_color) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->pointer); + WINPR_ASSERT(ps->update->pointer->PointerColor); + WLog_DBG(TAG, "called"); + return ps->update->pointer->PointerColor(ps, pointer_color); +} + +static BOOL pf_client_send_pointer_large(rdpContext* context, + const POINTER_LARGE_UPDATE* pointer_large) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->pointer); + WINPR_ASSERT(ps->update->pointer->PointerLarge); + WLog_DBG(TAG, "called"); + return ps->update->pointer->PointerLarge(ps, pointer_large); +} + +static BOOL pf_client_send_pointer_new(rdpContext* context, const POINTER_NEW_UPDATE* pointer_new) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->pointer); + WINPR_ASSERT(ps->update->pointer->PointerNew); + WLog_DBG(TAG, "called"); + return ps->update->pointer->PointerNew(ps, pointer_new); +} + +static BOOL pf_client_send_pointer_cached(rdpContext* context, + const POINTER_CACHED_UPDATE* pointer_cached) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->pointer); + WINPR_ASSERT(ps->update->pointer->PointerCached); + WLog_DBG(TAG, "called"); + return ps->update->pointer->PointerCached(ps, pointer_cached); +} + +static BOOL pf_client_save_session_info(rdpContext* context, UINT32 type, void* data) +{ + logon_info* logonInfo = NULL; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->SaveSessionInfo); + + WLog_DBG(TAG, "called"); + + switch (type) + { + case INFO_TYPE_LOGON: + case INFO_TYPE_LOGON_LONG: + { + logonInfo = (logon_info*)data; + PROXY_LOG_INFO(TAG, pc, "client logon info: Username: %s, Domain: %s", + logonInfo->username, logonInfo->domain); + break; + } + + default: + break; + } + + return ps->update->SaveSessionInfo(ps, type, data); +} + +static BOOL pf_client_server_status_info(rdpContext* context, UINT32 status) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->ServerStatusInfo); + + WLog_DBG(TAG, "called"); + return ps->update->ServerStatusInfo(ps, status); +} + +static BOOL pf_client_set_keyboard_indicators(rdpContext* context, UINT16 led_flags) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->SetKeyboardIndicators); + + WLog_DBG(TAG, "called"); + return ps->update->SetKeyboardIndicators(ps, led_flags); +} + +static BOOL pf_client_set_keyboard_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode) +{ + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->SetKeyboardImeStatus); + + WLog_DBG(TAG, "called"); + return ps->update->SetKeyboardImeStatus(ps, imeId, imeState, imeConvMode); +} + +static BOOL pf_client_window_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_STATE_ORDER* windowState) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->WindowCreate); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->WindowCreate(ps, orderInfo, windowState); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_window_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_STATE_ORDER* windowState) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->WindowUpdate); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->WindowUpdate(ps, orderInfo, windowState); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_ICON_ORDER* windowIcon) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->WindowIcon); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->WindowIcon(ps, orderInfo, windowIcon); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_CACHED_ICON_ORDER* windowCachedIcon) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->WindowCachedIcon); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->WindowCachedIcon(ps, orderInfo, windowCachedIcon); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->WindowDelete); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->WindowDelete(ps, orderInfo); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->NotifyIconCreate); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->NotifyIconCreate(ps, orderInfo, notifyIconState); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->NotifyIconUpdate); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->NotifyIconUpdate(ps, orderInfo, notifyIconState); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + BOOL rc = 0; + + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->NotifyIconDelete); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->NotifyIconDelete(ps, orderInfo); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const MONITORED_DESKTOP_ORDER* monitoredDesktop) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->MonitoredDesktop); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->MonitoredDesktop(ps, orderInfo, monitoredDesktop); + rdp_update_unlock(ps->update); + return rc; +} + +static BOOL pf_client_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + BOOL rc = 0; + pClientContext* pc = (pClientContext*)context; + proxyData* pdata = NULL; + rdpContext* ps = NULL; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + ps = (rdpContext*)pdata->ps; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->update); + WINPR_ASSERT(ps->update->window); + WINPR_ASSERT(ps->update->window->NonMonitoredDesktop); + + WLog_DBG(TAG, "called"); + rdp_update_lock(ps->update); + rc = ps->update->window->NonMonitoredDesktop(ps, orderInfo); + rdp_update_unlock(ps->update); + return rc; +} + +void pf_server_register_update_callbacks(rdpUpdate* update) +{ + WINPR_ASSERT(update); + update->RefreshRect = pf_server_refresh_rect; + update->SuppressOutput = pf_server_suppress_output; +} + +void pf_client_register_update_callbacks(rdpUpdate* update) +{ + WINPR_ASSERT(update); + update->BeginPaint = pf_client_begin_paint; + update->EndPaint = pf_client_end_paint; + update->BitmapUpdate = pf_client_bitmap_update; + update->DesktopResize = pf_client_desktop_resize; + update->RemoteMonitors = pf_client_remote_monitors; + update->SaveSessionInfo = pf_client_save_session_info; + update->ServerStatusInfo = pf_client_server_status_info; + update->SetKeyboardIndicators = pf_client_set_keyboard_indicators; + update->SetKeyboardImeStatus = pf_client_set_keyboard_ime_status; + + /* Rail window updates */ + update->window->WindowCreate = pf_client_window_create; + update->window->WindowUpdate = pf_client_window_update; + update->window->WindowIcon = pf_client_window_icon; + update->window->WindowCachedIcon = pf_client_window_cached_icon; + update->window->WindowDelete = pf_client_window_delete; + update->window->NotifyIconCreate = pf_client_notify_icon_create; + update->window->NotifyIconUpdate = pf_client_notify_icon_update; + update->window->NotifyIconDelete = pf_client_notify_icon_delete; + update->window->MonitoredDesktop = pf_client_monitored_desktop; + update->window->NonMonitoredDesktop = pf_client_non_monitored_desktop; + + /* Pointer updates */ + update->pointer->PointerSystem = pf_client_send_pointer_system; + update->pointer->PointerPosition = pf_client_send_pointer_position; + update->pointer->PointerColor = pf_client_send_pointer_color; + update->pointer->PointerLarge = pf_client_send_pointer_large; + update->pointer->PointerNew = pf_client_send_pointer_new; + update->pointer->PointerCached = pf_client_send_pointer_cached; +} diff --git a/server/proxy/pf_update.h b/server/proxy/pf_update.h new file mode 100644 index 0000000..81a9c32 --- /dev/null +++ b/server/proxy/pf_update.h @@ -0,0 +1,34 @@ +/** + * 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> + * + * 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. + */ + +#ifndef FREERDP_SERVER_PROXY_PFUPDATE_H +#define FREERDP_SERVER_PROXY_PFUPDATE_H + +#include <freerdp/freerdp.h> +#include <freerdp/gdi/gdi.h> +#include <freerdp/gdi/bitmap.h> + +#include <freerdp/server/proxy/proxy_context.h> + +void pf_server_register_update_callbacks(rdpUpdate* update); +void pf_client_register_update_callbacks(rdpUpdate* update); + +#endif /* FREERDP_SERVER_PROXY_PFUPDATE_H */ diff --git a/server/proxy/pf_utils.c b/server/proxy/pf_utils.c new file mode 100644 index 0000000..f8c17af --- /dev/null +++ b/server/proxy/pf_utils.c @@ -0,0 +1,95 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak <armin.novak@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 <winpr/assert.h> +#include <winpr/string.h> +#include <winpr/wtsapi.h> + +#include <freerdp/server/proxy/proxy_log.h> +#include "pf_utils.h" + +#define TAG PROXY_TAG("utils") + +pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name) +{ + pf_utils_channel_mode rc = PF_UTILS_CHANNEL_NOT_HANDLED; + BOOL found = FALSE; + + WINPR_ASSERT(config); + WINPR_ASSERT(name); + + for (size_t i = 0; i < config->InterceptCount; i++) + { + const char* channel_name = config->Intercept[i]; + if (strcmp(name, channel_name) == 0) + { + rc = PF_UTILS_CHANNEL_INTERCEPT; + goto end; + } + } + + for (size_t i = 0; i < config->PassthroughCount; i++) + { + const char* channel_name = config->Passthrough[i]; + if (strcmp(name, channel_name) == 0) + { + found = TRUE; + break; + } + } + + if (found) + { + if (config->PassthroughIsBlacklist) + rc = PF_UTILS_CHANNEL_BLOCK; + else + rc = PF_UTILS_CHANNEL_PASSTHROUGH; + } + else if (config->PassthroughIsBlacklist) + rc = PF_UTILS_CHANNEL_PASSTHROUGH; + +end: + WLog_DBG(TAG, "%s -> %s", name, pf_utils_channel_mode_string(rc)); + return rc; +} + +BOOL pf_utils_is_passthrough(const proxyConfig* config) +{ + WINPR_ASSERT(config); + + /* TODO: For the time being only passthrough mode is supported. */ + return TRUE; +} + +const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode) +{ + switch (mode) + { + case PF_UTILS_CHANNEL_BLOCK: + return "blocked"; + case PF_UTILS_CHANNEL_PASSTHROUGH: + return "passthrough"; + case PF_UTILS_CHANNEL_INTERCEPT: + return "intercepted"; + case PF_UTILS_CHANNEL_NOT_HANDLED: + default: + return "ignored"; + } +} diff --git a/server/proxy/pf_utils.h b/server/proxy/pf_utils.h new file mode 100644 index 0000000..0e899e9 --- /dev/null +++ b/server/proxy/pf_utils.h @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak <armin.novak@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. + */ + +#ifndef FREERDP_SERVER_PROXY_PFUTILS_H +#define FREERDP_SERVER_PROXY_PFUTILS_H + +#include <freerdp/server/proxy/proxy_config.h> +#include <freerdp/server/proxy/proxy_context.h> + +/** + * @brief pf_utils_channel_is_passthrough Checks of a channel identified by 'name' + * should be handled as passthrough. + * + * @param config The proxy configuration to check against. Must NOT be NULL. + * @param name The name of the channel. Must NOT be NULL. + * @return -1 if the channel is not handled, 0 if the channel should be ignored, + * 1 if the channel should be passed, 2 the channel will be intercepted + * e.g. proxy client and server are termination points and data passed + * between. + */ +pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name); +const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode); + +BOOL pf_utils_is_passthrough(const proxyConfig* config); + +#endif /* FREERDP_SERVER_PROXY_PFUTILS_H */ diff --git a/server/proxy/proxy_modules.h b/server/proxy/proxy_modules.h new file mode 100644 index 0000000..d5883a5 --- /dev/null +++ b/server/proxy/proxy_modules.h @@ -0,0 +1,100 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com> + * Copyright 2019 Idan Freiberg <speidy@gmail.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. + */ + +#ifndef FREERDP_SERVER_PROXY_MODULES_H +#define FREERDP_SERVER_PROXY_MODULES_H + +#include <winpr/wtypes.h> +#include <winpr/collections.h> + +#include <freerdp/server/proxy/proxy_modules_api.h> + +typedef enum +{ + FILTER_TYPE_KEYBOARD, /* proxyKeyboardEventInfo */ + FILTER_TYPE_UNICODE, /* proxyUnicodeEventInfo */ + FILTER_TYPE_MOUSE, /* proxyMouseEventInfo */ + FILTER_TYPE_MOUSE_EX, /* proxyMouseExEventInfo */ + FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, /* proxyChannelDataEventInfo */ + FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, /* proxyChannelDataEventInfo */ + FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, /* proxyChannelDataEventInfo */ + FILTER_TYPE_SERVER_FETCH_TARGET_ADDR, /* proxyFetchTargetEventInfo */ + FILTER_TYPE_SERVER_PEER_LOGON, /* proxyServerPeerLogon */ + FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, /* proxyChannelDataEventInfo */ + + FILTER_TYPE_STATIC_INTERCEPT_LIST, /* proxyChannelToInterceptData */ + FILTER_TYPE_DYN_INTERCEPT_LIST, /* proxyChannelToInterceptData */ + FILTER_TYPE_INTERCEPT_CHANNEL, /* proxyDynChannelInterceptData */ + FILTER_LAST +} PF_FILTER_TYPE; + +typedef enum +{ + HOOK_TYPE_CLIENT_INIT_CONNECT, + HOOK_TYPE_CLIENT_UNINIT_CONNECT, + HOOK_TYPE_CLIENT_PRE_CONNECT, + HOOK_TYPE_CLIENT_POST_CONNECT, + HOOK_TYPE_CLIENT_POST_DISCONNECT, + HOOK_TYPE_CLIENT_REDIRECT, + HOOK_TYPE_CLIENT_VERIFY_X509, + HOOK_TYPE_CLIENT_LOGIN_FAILURE, + HOOK_TYPE_CLIENT_END_PAINT, + HOOK_TYPE_CLIENT_LOAD_CHANNELS, + + HOOK_TYPE_SERVER_POST_CONNECT, + HOOK_TYPE_SERVER_ACTIVATE, + HOOK_TYPE_SERVER_CHANNELS_INIT, + HOOK_TYPE_SERVER_CHANNELS_FREE, + HOOK_TYPE_SERVER_SESSION_END, + HOOK_TYPE_SERVER_SESSION_INITIALIZE, + HOOK_TYPE_SERVER_SESSION_STARTED, + + HOOK_LAST +} PF_HOOK_TYPE; + +#ifdef __cplusplus +extern "C" +{ +#endif + + proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count); + + /** + * @brief pf_modules_add Registers a new plugin + * @param ep A module entry point function, must NOT be NULL + * @return TRUE for success, FALSE otherwise + */ + BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata); + + BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name); + void pf_modules_list_loaded_plugins(proxyModule* module); + + BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata, + void* param); + BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata, + void* custom); + + void pf_modules_free(proxyModule* module); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_PROXY_MODULES_H */ diff --git a/server/shadow/CMakeLists.txt b/server/shadow/CMakeLists.txt new file mode 100644 index 0000000..72adcca --- /dev/null +++ b/server/shadow/CMakeLists.txt @@ -0,0 +1,245 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Shadow Server cmake build script +# +# Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + +# freerdp-shadow library + +set(MODULE_NAME "freerdp-shadow") + +set(SRCS + shadow_client.c + shadow_client.h + shadow_lobby.c + shadow_lobby.h + shadow_input.c + shadow_input.h + shadow_screen.c + shadow_screen.h + shadow_surface.c + shadow_surface.h + shadow_encoder.c + shadow_encoder.h + shadow_capture.c + shadow_capture.h + shadow_channels.c + shadow_channels.h + shadow_encomsp.c + shadow_encomsp.h + shadow_remdesk.c + shadow_remdesk.h + shadow_rdpsnd.c + shadow_rdpsnd.h + shadow_audin.c + shadow_audin.h + shadow_rdpgfx.c + shadow_rdpgfx.h + shadow_subsystem.c + shadow_subsystem.h + shadow_mcevent.c + shadow_mcevent.h + shadow_server.c + shadow.h) + +if (NOT FREERDP_UNIFIED_BUILD) + find_package(rdtk 0 REQUIRED) + include_directories(${RDTK_INCLUDE_DIR}) +else() + if (NOT WITH_RDTK) + message(FATAL_ERROR "-DWITH_RDTK=ON is required for unified FreeRDP build with shadow server") + endif() + include_directories(${PROJECT_SOURCE_DIR}/rdtk/include) + include_directories(${PROJECT_BINARY_DIR}/rdtk/include) +endif() + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_PATCH 0) + set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" ) + + configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + +list (APPEND SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +add_library(${MODULE_NAME} ${SRCS}) + +list(APPEND LIBS + freerdp + freerdp-server + winpr + winpr-tools + rdtk +) + +target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>) +target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS}) + +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_VERSION_MAJOR}) +if (WITH_LIBRARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION}) +endif() + +install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ShadowTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if (WITH_DEBUG_SYMBOLS AND MSVC) + get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME) + install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow") + +# subsystem library + +set(MODULE_NAME "freerdp-shadow-subsystem") + +set(SRCS + shadow_subsystem_builtin.c) + +if(WIN32) + add_subdirectory(Win) +elseif(NOT APPLE) + add_subdirectory(X11) +elseif(APPLE AND NOT IOS) + add_subdirectory(Mac) +endif() + +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + + configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +add_library(${MODULE_NAME} ${SRCS}) + +list(APPEND LIBS + freerdp-shadow-subsystem-impl + freerdp-shadow + freerdp + winpr +) + +target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>) +target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS}) + +set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION}) +if (WITH_LIBRARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION}) +endif() + +if (NOT BUILD_SHARED_LIBS) + install(TARGETS freerdp-shadow-subsystem-impl + DESTINATION ${CMAKE_INSTALL_LIBDIR} + EXPORT FreeRDP-ShadowTargets + ) +endif() + +install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ShadowTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if (WITH_DEBUG_SYMBOLS AND MSVC) + get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME) + install(FILES ${PROJECT_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow") + +# command-line executable + +set(MODULE_NAME "freerdp-shadow-cli") + +set(SRCS + shadow.c) + +# On windows create dll version information. +# Vendor, product and year are already set in top level CMakeLists.txt +if (WIN32) + set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) + set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR}) + set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION}) + set (RC_VERSION_FILE "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" ) + + configure_file( + ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set ( SRCS ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +add_executable(${MODULE_NAME} ${SRCS}) + +set(MANPAGE_NAME "${MODULE_NAME}") +if (WITH_BINARY_VERSIONING) + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME "${MODULE_NAME}${FREERDP_API_VERSION}") + set(MANPAGE_NAME "${MODULE_NAME}${FREERDP_API_VERSION}") +endif() + +list(APPEND LIBS freerdp-shadow-subsystem freerdp-shadow freerdp winpr) + +target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS}) + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server) +if (WITH_DEBUG_SYMBOLS AND MSVC) + install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT symbols) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/shadow") + +include(pkg-config-install-prefix) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-shadow.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp-shadow${FREERDP_VERSION_MAJOR}.pc @ONLY) +configure_file(freerdp-shadow-cli.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1) +install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME}.1 1) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp-shadow${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR}) + +export(PACKAGE freerdp-shadow) + +SetFreeRDPCMakeInstallDir(FREERDP_SERVER_CMAKE_INSTALL_DIR "FreeRDP-Shadow${FREERDP_VERSION_MAJOR}") + +configure_package_config_file(FreeRDP-ShadowConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfig.cmake + INSTALL_DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR} + PATH_VARS FREERDP_INCLUDE_DIR) + +write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfigVersion.cmake + VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ShadowConfigVersion.cmake + DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR}) + +install(EXPORT FreeRDP-ShadowTargets DESTINATION ${FREERDP_SERVER_CMAKE_INSTALL_DIR}) diff --git a/server/shadow/FreeRDP-ShadowConfig.cmake.in b/server/shadow/FreeRDP-ShadowConfig.cmake.in new file mode 100644 index 0000000..9b6f24c --- /dev/null +++ b/server/shadow/FreeRDP-ShadowConfig.cmake.in @@ -0,0 +1,14 @@ +include(CMakeFindDependencyMacro) +find_dependency(WinPR @FREERDP_VERSION@) +find_dependency(FreeRDP @FREERDP_VERSION@) +find_dependency(FreeRDP-Server @FREERDP_VERSION@) + +@PACKAGE_INIT@ + +set(FreeRDP-Shadow_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@") +set(FreeRDP-Shadow_VERSION_MINOR "@FREERDP_VERSION_MINOR@") +set(FreeRDP-Shadow_VERSION_REVISION "@FREERDP_VERSION_REVISION@") + +set_and_check(FreeRDP-Shadow_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@") + +include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ShadowTargets.cmake") diff --git a/server/shadow/Mac/CMakeLists.txt b/server/shadow/Mac/CMakeLists.txt new file mode 100644 index 0000000..20c9a7b --- /dev/null +++ b/server/shadow/Mac/CMakeLists.txt @@ -0,0 +1,31 @@ + +include (WarnUnmaintained) +warn_unmaintained("mac shadow server subsystem") + +find_library(IOKIT IOKit REQUIRED) +find_library(IOSURFACE IOSurface REQUIRED) +find_library(CARBON Carbon REQUIRED) +find_package(PAM) + +set(LIBS + ${IOKIT} + ${IOSURFACE} + ${CARBON} +) + +if(PAM_FOUND) + add_definitions(-DWITH_PAM) + include_directories(${PAM_INCLUDE_DIR}) + list(APPEND LIBS ${PAM_LIBRARY}) +else() + message("building without PAM authentication support") +endif() + +add_definitions(-DWITH_SHADOW_MAC) +add_library(freerdp-shadow-subsystem-impl STATIC + mac_shadow.h + mac_shadow.c +) +target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE + ${LIBS} +) diff --git a/server/shadow/Mac/mac_shadow.c b/server/shadow/Mac/mac_shadow.c new file mode 100644 index 0000000..ba6d2b0 --- /dev/null +++ b/server/shadow/Mac/mac_shadow.c @@ -0,0 +1,680 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.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/crt.h> +#include <winpr/synch.h> +#include <winpr/input.h> +#include <winpr/sysinfo.h> + +#include <freerdp/server/server-common.h> +#include <freerdp/codec/color.h> +#include <freerdp/codec/region.h> +#include <freerdp/log.h> + +#include "mac_shadow.h" + +#define TAG SERVER_TAG("shadow.mac") + +static macShadowSubsystem* g_Subsystem = NULL; + +static BOOL mac_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT32 flags) +{ + if (!subsystem || !client) + return FALSE; + + return TRUE; +} + +static BOOL mac_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client, + UINT16 flags, UINT8 code) +{ + DWORD vkcode; + DWORD keycode; + BOOL extended; + CGEventRef kbdEvent; + CGEventSourceRef source; + extended = (flags & KBD_FLAGS_EXTENDED) ? TRUE : FALSE; + + if (!subsystem || !client) + return FALSE; + + if (extended) + code |= KBDEXT; + + vkcode = GetVirtualKeyCodeFromVirtualScanCode(code, 4); + + if (extended) + vkcode |= KBDEXT; + + keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_APPLE); + + source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + + if (flags & KBD_FLAGS_DOWN) + { + kbdEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keycode, TRUE); + CGEventPost(kCGHIDEventTap, kbdEvent); + CFRelease(kbdEvent); + } + else if (flags & KBD_FLAGS_RELEASE) + { + kbdEvent = CGEventCreateKeyboardEvent(source, (CGKeyCode)keycode, FALSE); + CGEventPost(kCGHIDEventTap, kbdEvent); + CFRelease(kbdEvent); + } + + CFRelease(source); + return TRUE; +} + +static BOOL mac_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT16 flags, + UINT16 code) +{ + if (!subsystem || !client) + return FALSE; + + return TRUE; +} + +static BOOL mac_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client, + UINT16 flags, UINT16 x, UINT16 y) +{ + macShadowSubsystem* mac = (macShadowSubsystem*)subsystem; + UINT32 scrollX = 0; + UINT32 scrollY = 0; + CGWheelCount wheelCount = 2; + + if (!subsystem || !client) + return FALSE; + + if (flags & PTR_FLAGS_WHEEL) + { + scrollY = flags & WheelRotationMask; + + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + { + scrollY = -(flags & WheelRotationMask) / 392; + } + else + { + scrollY = (flags & WheelRotationMask) / 120; + } + + CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef scroll = CGEventCreateScrollWheelEvent(source, kCGScrollEventUnitLine, + wheelCount, scrollY, scrollX); + CGEventPost(kCGHIDEventTap, scroll); + CFRelease(scroll); + CFRelease(source); + } + else + { + CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventType mouseType = kCGEventNull; + CGMouseButton mouseButton = kCGMouseButtonLeft; + + if (flags & PTR_FLAGS_MOVE) + { + if (mac->mouseDownLeft) + mouseType = kCGEventLeftMouseDragged; + else if (mac->mouseDownRight) + mouseType = kCGEventRightMouseDragged; + else if (mac->mouseDownOther) + mouseType = kCGEventOtherMouseDragged; + else + mouseType = kCGEventMouseMoved; + + CGEventRef move = + CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton); + CGEventPost(kCGHIDEventTap, move); + CFRelease(move); + } + + if (flags & PTR_FLAGS_BUTTON1) + { + mouseButton = kCGMouseButtonLeft; + + if (flags & PTR_FLAGS_DOWN) + { + mouseType = kCGEventLeftMouseDown; + mac->mouseDownLeft = TRUE; + } + else + { + mouseType = kCGEventLeftMouseUp; + mac->mouseDownLeft = FALSE; + } + } + else if (flags & PTR_FLAGS_BUTTON2) + { + mouseButton = kCGMouseButtonRight; + + if (flags & PTR_FLAGS_DOWN) + { + mouseType = kCGEventRightMouseDown; + mac->mouseDownRight = TRUE; + } + else + { + mouseType = kCGEventRightMouseUp; + mac->mouseDownRight = FALSE; + } + } + else if (flags & PTR_FLAGS_BUTTON3) + { + mouseButton = kCGMouseButtonCenter; + + if (flags & PTR_FLAGS_DOWN) + { + mouseType = kCGEventOtherMouseDown; + mac->mouseDownOther = TRUE; + } + else + { + mouseType = kCGEventOtherMouseUp; + mac->mouseDownOther = FALSE; + } + } + + CGEventRef mouseEvent = + CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton); + CGEventPost(kCGHIDEventTap, mouseEvent); + CFRelease(mouseEvent); + CFRelease(source); + } + + return TRUE; +} + +static BOOL mac_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT16 flags, UINT16 x, + UINT16 y) +{ + if (!subsystem || !client) + return FALSE; + + return TRUE; +} + +static int mac_shadow_detect_monitors(macShadowSubsystem* subsystem) +{ + size_t wide, high; + MONITOR_DEF* monitor; + CGDirectDisplayID displayId; + displayId = CGMainDisplayID(); + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId); + subsystem->pixelWidth = CGDisplayModeGetPixelWidth(mode); + subsystem->pixelHeight = CGDisplayModeGetPixelHeight(mode); + wide = CGDisplayPixelsWide(displayId); + high = CGDisplayPixelsHigh(displayId); + CGDisplayModeRelease(mode); + subsystem->retina = ((subsystem->pixelWidth / wide) == 2) ? TRUE : FALSE; + + if (subsystem->retina) + { + subsystem->width = wide; + subsystem->height = high; + } + else + { + subsystem->width = subsystem->pixelWidth; + subsystem->height = subsystem->pixelHeight; + } + + subsystem->common.numMonitors = 1; + monitor = &(subsystem->common.monitors[0]); + monitor->left = 0; + monitor->top = 0; + monitor->right = subsystem->width; + monitor->bottom = subsystem->height; + monitor->flags = 1; + return 1; +} + +static int mac_shadow_capture_start(macShadowSubsystem* subsystem) +{ + CGError err; + err = CGDisplayStreamStart(subsystem->stream); + + if (err != kCGErrorSuccess) + return -1; + + return 1; +} + +static int mac_shadow_capture_stop(macShadowSubsystem* subsystem) +{ + CGError err; + err = CGDisplayStreamStop(subsystem->stream); + + if (err != kCGErrorSuccess) + return -1; + + return 1; +} + +static int mac_shadow_capture_get_dirty_region(macShadowSubsystem* subsystem) +{ + size_t numRects; + const CGRect* rects; + RECTANGLE_16 invalidRect; + rdpShadowSurface* surface = subsystem->common.server->surface; + rects = CGDisplayStreamUpdateGetRects(subsystem->lastUpdate, kCGDisplayStreamUpdateDirtyRects, + &numRects); + + if (!numRects) + return -1; + + for (size_t index = 0; index < numRects; index++) + { + invalidRect.left = (UINT16)rects[index].origin.x; + invalidRect.top = (UINT16)rects[index].origin.y; + invalidRect.right = invalidRect.left + (UINT16)rects[index].size.width; + invalidRect.bottom = invalidRect.top + (UINT16)rects[index].size.height; + + if (subsystem->retina) + { + /* scale invalid rect */ + invalidRect.left /= 2; + invalidRect.top /= 2; + invalidRect.right /= 2; + invalidRect.bottom /= 2; + } + + region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect); + } + + return 0; +} + +static int freerdp_image_copy_from_retina(BYTE* pDstData, DWORD DstFormat, int nDstStep, int nXDst, + int nYDst, int nWidth, int nHeight, BYTE* pSrcData, + int nSrcStep, int nXSrc, int nYSrc) +{ + BYTE* pSrcPixel; + BYTE* pDstPixel; + int nSrcPad; + int nDstPad; + int srcBitsPerPixel; + int srcBytesPerPixel; + int dstBitsPerPixel; + int dstBytesPerPixel; + srcBitsPerPixel = 24; + srcBytesPerPixel = 8; + + if (nSrcStep < 0) + nSrcStep = srcBytesPerPixel * nWidth; + + dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat); + dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat); + + if (nDstStep < 0) + nDstStep = dstBytesPerPixel * nWidth; + + nSrcPad = (nSrcStep - (nWidth * srcBytesPerPixel)); + nDstPad = (nDstStep - (nWidth * dstBytesPerPixel)); + pSrcPixel = &pSrcData[(nYSrc * nSrcStep) + (nXSrc * 4)]; + pDstPixel = &pDstData[(nYDst * nDstStep) + (nXDst * 4)]; + + for (int y = 0; y < nHeight; y++) + { + for (int x = 0; x < nWidth; x++) + { + UINT32 R, G, B; + UINT32 color; + /* simple box filter scaling, could be improved with better algorithm */ + B = pSrcPixel[0] + pSrcPixel[4] + pSrcPixel[nSrcStep + 0] + pSrcPixel[nSrcStep + 4]; + G = pSrcPixel[1] + pSrcPixel[5] + pSrcPixel[nSrcStep + 1] + pSrcPixel[nSrcStep + 5]; + R = pSrcPixel[2] + pSrcPixel[6] + pSrcPixel[nSrcStep + 2] + pSrcPixel[nSrcStep + 6]; + pSrcPixel += 8; + color = FreeRDPGetColor(DstFormat, R >> 2, G >> 2, B >> 2, 0xFF); + FreeRDPWriteColor(pDstPixel, DstFormat, color); + pDstPixel += dstBytesPerPixel; + } + + pSrcPixel = &pSrcPixel[nSrcPad + nSrcStep]; + pDstPixel = &pDstPixel[nDstPad]; + } + + return 1; +} + +static void (^mac_capture_stream_handler)( + CGDisplayStreamFrameStatus, uint64_t, IOSurfaceRef, + CGDisplayStreamUpdateRef) = ^(CGDisplayStreamFrameStatus status, uint64_t displayTime, + IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) { + int x, y; + int count; + int width; + int height; + int nSrcStep; + BOOL empty; + BYTE* pSrcData; + RECTANGLE_16 surfaceRect; + const RECTANGLE_16* extents; + macShadowSubsystem* subsystem = g_Subsystem; + rdpShadowServer* server = subsystem->common.server; + rdpShadowSurface* surface = server->surface; + count = ArrayList_Count(server->clients); + + if (count < 1) + return; + + EnterCriticalSection(&(surface->lock)); + mac_shadow_capture_get_dirty_region(subsystem); + surfaceRect.left = 0; + surfaceRect.top = 0; + surfaceRect.right = surface->width; + surfaceRect.bottom = surface->height; + region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect); + empty = region16_is_empty(&(surface->invalidRegion)); + LeaveCriticalSection(&(surface->lock)); + + if (!empty) + { + extents = region16_extents(&(surface->invalidRegion)); + x = extents->left; + y = extents->top; + width = extents->right - extents->left; + height = extents->bottom - extents->top; + IOSurfaceLock(frameSurface, kIOSurfaceLockReadOnly, NULL); + pSrcData = (BYTE*)IOSurfaceGetBaseAddress(frameSurface); + nSrcStep = (int)IOSurfaceGetBytesPerRow(frameSurface); + + if (subsystem->retina) + { + freerdp_image_copy_from_retina(surface->data, surface->format, surface->scanline, x, y, + width, height, pSrcData, nSrcStep, x, y); + } + else + { + freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y, width, height, + pSrcData, PIXEL_FORMAT_BGRX32, nSrcStep, x, y, NULL, + FREERDP_FLIP_NONE); + } + LeaveCriticalSection(&(surface->lock)); + + IOSurfaceUnlock(frameSurface, kIOSurfaceLockReadOnly, NULL); + ArrayList_Lock(server->clients); + count = ArrayList_Count(server->clients); + shadow_subsystem_frame_update(&subsystem->common); + + if (count == 1) + { + rdpShadowClient* client; + client = (rdpShadowClient*)ArrayList_GetItem(server->clients, 0); + + if (client) + { + subsystem->common.captureFrameRate = shadow_encoder_preferred_fps(client->encoder); + } + } + + ArrayList_Unlock(server->clients); + EnterCriticalSection(&(surface->lock)); + region16_clear(&(surface->invalidRegion)); + LeaveCriticalSection(&(surface->lock)); + } + + if (status != kCGDisplayStreamFrameStatusFrameComplete) + { + switch (status) + { + case kCGDisplayStreamFrameStatusFrameIdle: + break; + + case kCGDisplayStreamFrameStatusStopped: + break; + + case kCGDisplayStreamFrameStatusFrameBlank: + break; + + default: + break; + } + } + else if (!subsystem->lastUpdate) + { + CFRetain(updateRef); + subsystem->lastUpdate = updateRef; + } + else + { + CGDisplayStreamUpdateRef tmpRef = subsystem->lastUpdate; + subsystem->lastUpdate = CGDisplayStreamUpdateCreateMergedUpdate(tmpRef, updateRef); + CFRelease(tmpRef); + } +}; + +static int mac_shadow_capture_init(macShadowSubsystem* subsystem) +{ + void* keys[2]; + void* values[2]; + CFDictionaryRef opts; + CGDirectDisplayID displayId; + displayId = CGMainDisplayID(); + subsystem->captureQueue = dispatch_queue_create("mac.shadow.capture", NULL); + keys[0] = (void*)kCGDisplayStreamShowCursor; + values[0] = (void*)kCFBooleanFalse; + opts = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1, + NULL, NULL); + subsystem->stream = CGDisplayStreamCreateWithDispatchQueue( + displayId, subsystem->pixelWidth, subsystem->pixelHeight, 'BGRA', opts, + subsystem->captureQueue, mac_capture_stream_handler); + CFRelease(opts); + return 1; +} + +static int mac_shadow_screen_grab(macShadowSubsystem* subsystem) +{ + return 1; +} + +static int mac_shadow_subsystem_process_message(macShadowSubsystem* subsystem, wMessage* message) +{ + rdpShadowServer* server = subsystem->common.server; + rdpShadowSurface* surface = server->surface; + + switch (message->id) + { + case SHADOW_MSG_IN_REFRESH_REQUEST_ID: + EnterCriticalSection(&(surface->lock)); + shadow_subsystem_frame_update((rdpShadowSubsystem*)subsystem); + LeaveCriticalSection(&(surface->lock)); + break; + + default: + WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id); + break; + } + + if (message->Free) + message->Free(message); + + return 1; +} + +static DWORD WINAPI mac_shadow_subsystem_thread(LPVOID arg) +{ + macShadowSubsystem* subsystem = (macShadowSubsystem*)arg; + DWORD status; + DWORD nCount; + UINT64 cTime; + DWORD dwTimeout; + DWORD dwInterval; + UINT64 frameTime; + HANDLE events[32]; + wMessage message; + wMessagePipe* MsgPipe; + MsgPipe = subsystem->common.MsgPipe; + nCount = 0; + events[nCount++] = MessageQueue_Event(MsgPipe->In); + subsystem->common.captureFrameRate = 16; + dwInterval = 1000 / subsystem->common.captureFrameRate; + frameTime = GetTickCount64() + dwInterval; + + while (1) + { + cTime = GetTickCount64(); + dwTimeout = (cTime > frameTime) ? 0 : frameTime - cTime; + status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout); + + if (WaitForSingleObject(MessageQueue_Event(MsgPipe->In), 0) == WAIT_OBJECT_0) + { + if (MessageQueue_Peek(MsgPipe->In, &message, TRUE)) + { + if (message.id == WMQ_QUIT) + break; + + mac_shadow_subsystem_process_message(subsystem, &message); + } + } + + if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime)) + { + mac_shadow_screen_grab(subsystem); + dwInterval = 1000 / subsystem->common.captureFrameRate; + frameTime += dwInterval; + } + } + + ExitThread(0); + return 0; +} + +static UINT32 mac_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors) +{ + int index; + size_t wide, high; + UINT32 numMonitors = 0; + MONITOR_DEF* monitor; + CGDirectDisplayID displayId; + displayId = CGMainDisplayID(); + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayId); + wide = CGDisplayPixelsWide(displayId); + high = CGDisplayPixelsHigh(displayId); + CGDisplayModeRelease(mode); + index = 0; + numMonitors = 1; + monitor = &monitors[index]; + monitor->left = 0; + monitor->top = 0; + monitor->right = (int)wide; + monitor->bottom = (int)high; + monitor->flags = 1; + return numMonitors; +} + +static int mac_shadow_subsystem_init(rdpShadowSubsystem* rdpsubsystem) +{ + macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem; + g_Subsystem = subsystem; + + mac_shadow_detect_monitors(subsystem); + mac_shadow_capture_init(subsystem); + return 1; +} + +static int mac_shadow_subsystem_uninit(rdpShadowSubsystem* rdpsubsystem) +{ + macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem; + if (!subsystem) + return -1; + + if (subsystem->lastUpdate) + { + CFRelease(subsystem->lastUpdate); + subsystem->lastUpdate = NULL; + } + + return 1; +} + +static int mac_shadow_subsystem_start(rdpShadowSubsystem* rdpsubsystem) +{ + macShadowSubsystem* subsystem = (macShadowSubsystem*)rdpsubsystem; + HANDLE thread; + + if (!subsystem) + return -1; + + mac_shadow_capture_start(subsystem); + + if (!(thread = CreateThread(NULL, 0, mac_shadow_subsystem_thread, (void*)subsystem, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create thread"); + return -1; + } + + return 1; +} + +static int mac_shadow_subsystem_stop(rdpShadowSubsystem* subsystem) +{ + if (!subsystem) + return -1; + + return 1; +} + +static void mac_shadow_subsystem_free(rdpShadowSubsystem* subsystem) +{ + if (!subsystem) + return; + + mac_shadow_subsystem_uninit(subsystem); + free(subsystem); +} + +static rdpShadowSubsystem* mac_shadow_subsystem_new(void) +{ + macShadowSubsystem* subsystem = calloc(1, sizeof(macShadowSubsystem)); + + if (!subsystem) + return NULL; + + subsystem->common.SynchronizeEvent = mac_shadow_input_synchronize_event; + subsystem->common.KeyboardEvent = mac_shadow_input_keyboard_event; + subsystem->common.UnicodeKeyboardEvent = mac_shadow_input_unicode_keyboard_event; + subsystem->common.MouseEvent = mac_shadow_input_mouse_event; + subsystem->common.ExtendedMouseEvent = mac_shadow_input_extended_mouse_event; + return &subsystem->common; +} + +FREERDP_API const char* ShadowSubsystemName(void) +{ + return "Mac"; +} + +FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints) +{ + char name[] = "mac shadow subsystem"; + char* arg[] = { name }; + + freerdp_server_warn_unmaintained(ARRAYSIZE(arg), arg); + pEntryPoints->New = mac_shadow_subsystem_new; + pEntryPoints->Free = mac_shadow_subsystem_free; + pEntryPoints->Init = mac_shadow_subsystem_init; + pEntryPoints->Uninit = mac_shadow_subsystem_uninit; + pEntryPoints->Start = mac_shadow_subsystem_start; + pEntryPoints->Stop = mac_shadow_subsystem_stop; + pEntryPoints->EnumMonitors = mac_shadow_enum_monitors; + return 1; +} diff --git a/server/shadow/Mac/mac_shadow.h b/server/shadow/Mac/mac_shadow.h new file mode 100644 index 0000000..2c31572 --- /dev/null +++ b/server/shadow/Mac/mac_shadow.h @@ -0,0 +1,64 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_MAC_SHADOW_H +#define FREERDP_SERVER_SHADOW_MAC_SHADOW_H + +#include <freerdp/server/shadow.h> + +typedef struct mac_shadow_subsystem macShadowSubsystem; + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/stream.h> +#include <winpr/collections.h> + +#include <dispatch/dispatch.h> +#include <IOKit/IOKitLib.h> +#include <IOSurface/IOSurface.h> +#include <CoreVideo/CoreVideo.h> +#include <CoreGraphics/CoreGraphics.h> + +struct mac_shadow_subsystem +{ + rdpShadowSubsystem common; + + int width; + int height; + BOOL retina; + int pixelWidth; + int pixelHeight; + BOOL mouseDownLeft; + BOOL mouseDownRight; + BOOL mouseDownOther; + CGDisplayStreamRef stream; + dispatch_queue_t captureQueue; + CGDisplayStreamUpdateRef lastUpdate; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_MAC_SHADOW_H */ diff --git a/server/shadow/Win/CMakeLists.txt b/server/shadow/Win/CMakeLists.txt new file mode 100644 index 0000000..f8a920e --- /dev/null +++ b/server/shadow/Win/CMakeLists.txt @@ -0,0 +1,20 @@ + +include (WarnUnmaintained) +warn_unmaintained("windows shadow server subsystem") + +add_definitions(-DWITH_SHADOW_WIN) +add_library(freerdp-shadow-subsystem-impl STATIC + win_dxgi.c + win_dxgi.h + win_rdp.c + win_rdp.h + win_shadow.c + win_shadow.h + win_wds.c + win_wds.h +) +target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE + freerdp-client + freerdp + winpr +) diff --git a/server/shadow/Win/win_dxgi.c b/server/shadow/Win/win_dxgi.c new file mode 100644 index 0000000..3c25a00 --- /dev/null +++ b/server/shadow/Win/win_dxgi.c @@ -0,0 +1,794 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <freerdp/log.h> + +#include "win_dxgi.h" + +#define TAG SERVER_TAG("shadow.win") + +#ifdef WITH_DXGI_1_2 + +static D3D_DRIVER_TYPE DriverTypes[] = { + D3D_DRIVER_TYPE_HARDWARE, + D3D_DRIVER_TYPE_WARP, + D3D_DRIVER_TYPE_REFERENCE, +}; + +static UINT NumDriverTypes = ARRAYSIZE(DriverTypes); + +static D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 }; + +static UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels); + +static HMODULE d3d11_module = NULL; + +typedef HRESULT(WINAPI* fnD3D11CreateDevice)(IDXGIAdapter* pAdapter, D3D_DRIVER_TYPE DriverType, + HMODULE Software, UINT Flags, + CONST D3D_FEATURE_LEVEL* pFeatureLevels, + UINT FeatureLevels, UINT SDKVersion, + ID3D11Device** ppDevice, + D3D_FEATURE_LEVEL* pFeatureLevel, + ID3D11DeviceContext** ppImmediateContext); + +static fnD3D11CreateDevice pfnD3D11CreateDevice = NULL; + +#undef DEFINE_GUID +#define INITGUID + +#include <initguid.h> + +/* d3d11.h GUIDs */ + +DEFINE_GUID(IID_ID3D11DeviceChild, 0x1841e5c8, 0x16b0, 0x489b, 0xbc, 0xc8, 0x44, 0xcf, 0xb0, 0xd5, + 0xde, 0xae); +DEFINE_GUID(IID_ID3D11DepthStencilState, 0x03823efb, 0x8d8f, 0x4e1c, 0x9a, 0xa2, 0xf6, 0x4b, 0xb2, + 0xcb, 0xfd, 0xf1); +DEFINE_GUID(IID_ID3D11BlendState, 0x75b68faa, 0x347d, 0x4159, 0x8f, 0x45, 0xa0, 0x64, 0x0f, 0x01, + 0xcd, 0x9a); +DEFINE_GUID(IID_ID3D11RasterizerState, 0x9bb4ab81, 0xab1a, 0x4d8f, 0xb5, 0x06, 0xfc, 0x04, 0x20, + 0x0b, 0x6e, 0xe7); +DEFINE_GUID(IID_ID3D11Resource, 0xdc8e63f3, 0xd12b, 0x4952, 0xb4, 0x7b, 0x5e, 0x45, 0x02, 0x6a, + 0x86, 0x2d); +DEFINE_GUID(IID_ID3D11Buffer, 0x48570b85, 0xd1ee, 0x4fcd, 0xa2, 0x50, 0xeb, 0x35, 0x07, 0x22, 0xb0, + 0x37); +DEFINE_GUID(IID_ID3D11Texture1D, 0xf8fb5c27, 0xc6b3, 0x4f75, 0xa4, 0xc8, 0x43, 0x9a, 0xf2, 0xef, + 0x56, 0x4c); +DEFINE_GUID(IID_ID3D11Texture2D, 0x6f15aaf2, 0xd208, 0x4e89, 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3, + 0x4f, 0x9c); +DEFINE_GUID(IID_ID3D11Texture3D, 0x037e866e, 0xf56d, 0x4357, 0xa8, 0xaf, 0x9d, 0xab, 0xbe, 0x6e, + 0x25, 0x0e); +DEFINE_GUID(IID_ID3D11View, 0x839d1216, 0xbb2e, 0x412b, 0xb7, 0xf4, 0xa9, 0xdb, 0xeb, 0xe0, 0x8e, + 0xd1); +DEFINE_GUID(IID_ID3D11ShaderResourceView, 0xb0e06fe0, 0x8192, 0x4e1a, 0xb1, 0xca, 0x36, 0xd7, 0x41, + 0x47, 0x10, 0xb2); +DEFINE_GUID(IID_ID3D11RenderTargetView, 0xdfdba067, 0x0b8d, 0x4865, 0x87, 0x5b, 0xd7, 0xb4, 0x51, + 0x6c, 0xc1, 0x64); +DEFINE_GUID(IID_ID3D11DepthStencilView, 0x9fdac92a, 0x1876, 0x48c3, 0xaf, 0xad, 0x25, 0xb9, 0x4f, + 0x84, 0xa9, 0xb6); +DEFINE_GUID(IID_ID3D11UnorderedAccessView, 0x28acf509, 0x7f5c, 0x48f6, 0x86, 0x11, 0xf3, 0x16, 0x01, + 0x0a, 0x63, 0x80); +DEFINE_GUID(IID_ID3D11VertexShader, 0x3b301d64, 0xd678, 0x4289, 0x88, 0x97, 0x22, 0xf8, 0x92, 0x8b, + 0x72, 0xf3); +DEFINE_GUID(IID_ID3D11HullShader, 0x8e5c6061, 0x628a, 0x4c8e, 0x82, 0x64, 0xbb, 0xe4, 0x5c, 0xb3, + 0xd5, 0xdd); +DEFINE_GUID(IID_ID3D11DomainShader, 0xf582c508, 0x0f36, 0x490c, 0x99, 0x77, 0x31, 0xee, 0xce, 0x26, + 0x8c, 0xfa); +DEFINE_GUID(IID_ID3D11GeometryShader, 0x38325b96, 0xeffb, 0x4022, 0xba, 0x02, 0x2e, 0x79, 0x5b, + 0x70, 0x27, 0x5c); +DEFINE_GUID(IID_ID3D11PixelShader, 0xea82e40d, 0x51dc, 0x4f33, 0x93, 0xd4, 0xdb, 0x7c, 0x91, 0x25, + 0xae, 0x8c); +DEFINE_GUID(IID_ID3D11ComputeShader, 0x4f5b196e, 0xc2bd, 0x495e, 0xbd, 0x01, 0x1f, 0xde, 0xd3, 0x8e, + 0x49, 0x69); +DEFINE_GUID(IID_ID3D11InputLayout, 0xe4819ddc, 0x4cf0, 0x4025, 0xbd, 0x26, 0x5d, 0xe8, 0x2a, 0x3e, + 0x07, 0xb7); +DEFINE_GUID(IID_ID3D11SamplerState, 0xda6fea51, 0x564c, 0x4487, 0x98, 0x10, 0xf0, 0xd0, 0xf9, 0xb4, + 0xe3, 0xa5); +DEFINE_GUID(IID_ID3D11Asynchronous, 0x4b35d0cd, 0x1e15, 0x4258, 0x9c, 0x98, 0x1b, 0x13, 0x33, 0xf6, + 0xdd, 0x3b); +DEFINE_GUID(IID_ID3D11Query, 0xd6c00747, 0x87b7, 0x425e, 0xb8, 0x4d, 0x44, 0xd1, 0x08, 0x56, 0x0a, + 0xfd); +DEFINE_GUID(IID_ID3D11Predicate, 0x9eb576dd, 0x9f77, 0x4d86, 0x81, 0xaa, 0x8b, 0xab, 0x5f, 0xe4, + 0x90, 0xe2); +DEFINE_GUID(IID_ID3D11Counter, 0x6e8c49fb, 0xa371, 0x4770, 0xb4, 0x40, 0x29, 0x08, 0x60, 0x22, 0xb7, + 0x41); +DEFINE_GUID(IID_ID3D11ClassInstance, 0xa6cd7faa, 0xb0b7, 0x4a2f, 0x94, 0x36, 0x86, 0x62, 0xa6, 0x57, + 0x97, 0xcb); +DEFINE_GUID(IID_ID3D11ClassLinkage, 0xddf57cba, 0x9543, 0x46e4, 0xa1, 0x2b, 0xf2, 0x07, 0xa0, 0xfe, + 0x7f, 0xed); +DEFINE_GUID(IID_ID3D11CommandList, 0xa24bc4d1, 0x769e, 0x43f7, 0x80, 0x13, 0x98, 0xff, 0x56, 0x6c, + 0x18, 0xe2); +DEFINE_GUID(IID_ID3D11DeviceContext, 0xc0bfa96c, 0xe089, 0x44fb, 0x8e, 0xaf, 0x26, 0xf8, 0x79, 0x61, + 0x90, 0xda); +DEFINE_GUID(IID_ID3D11VideoDecoder, 0x3C9C5B51, 0x995D, 0x48d1, 0x9B, 0x8D, 0xFA, 0x5C, 0xAE, 0xDE, + 0xD6, 0x5C); +DEFINE_GUID(IID_ID3D11VideoProcessorEnumerator, 0x31627037, 0x53AB, 0x4200, 0x90, 0x61, 0x05, 0xFA, + 0xA9, 0xAB, 0x45, 0xF9); +DEFINE_GUID(IID_ID3D11VideoProcessor, 0x1D7B0652, 0x185F, 0x41c6, 0x85, 0xCE, 0x0C, 0x5B, 0xE3, + 0xD4, 0xAE, 0x6C); +DEFINE_GUID(IID_ID3D11AuthenticatedChannel, 0x3015A308, 0xDCBD, 0x47aa, 0xA7, 0x47, 0x19, 0x24, + 0x86, 0xD1, 0x4D, 0x4A); +DEFINE_GUID(IID_ID3D11CryptoSession, 0x9B32F9AD, 0xBDCC, 0x40a6, 0xA3, 0x9D, 0xD5, 0xC8, 0x65, 0x84, + 0x57, 0x20); +DEFINE_GUID(IID_ID3D11VideoDecoderOutputView, 0xC2931AEA, 0x2A85, 0x4f20, 0x86, 0x0F, 0xFB, 0xA1, + 0xFD, 0x25, 0x6E, 0x18); +DEFINE_GUID(IID_ID3D11VideoProcessorInputView, 0x11EC5A5F, 0x51DC, 0x4945, 0xAB, 0x34, 0x6E, 0x8C, + 0x21, 0x30, 0x0E, 0xA5); +DEFINE_GUID(IID_ID3D11VideoProcessorOutputView, 0xA048285E, 0x25A9, 0x4527, 0xBD, 0x93, 0xD6, 0x8B, + 0x68, 0xC4, 0x42, 0x54); +DEFINE_GUID(IID_ID3D11VideoContext, 0x61F21C45, 0x3C0E, 0x4a74, 0x9C, 0xEA, 0x67, 0x10, 0x0D, 0x9A, + 0xD5, 0xE4); +DEFINE_GUID(IID_ID3D11VideoDevice, 0x10EC4D5B, 0x975A, 0x4689, 0xB9, 0xE4, 0xD0, 0xAA, 0xC3, 0x0F, + 0xE3, 0x33); +DEFINE_GUID(IID_ID3D11Device, 0xdb6f6ddb, 0xac77, 0x4e88, 0x82, 0x53, 0x81, 0x9d, 0xf9, 0xbb, 0xf1, + 0x40); + +/* dxgi.h GUIDs */ + +DEFINE_GUID(IID_IDXGIObject, 0xaec22fb8, 0x76f3, 0x4639, 0x9b, 0xe0, 0x28, 0xeb, 0x43, 0xa6, 0x7a, + 0x2e); +DEFINE_GUID(IID_IDXGIDeviceSubObject, 0x3d3e0379, 0xf9de, 0x4d58, 0xbb, 0x6c, 0x18, 0xd6, 0x29, + 0x92, 0xf1, 0xa6); +DEFINE_GUID(IID_IDXGIResource, 0x035f3ab4, 0x482e, 0x4e50, 0xb4, 0x1f, 0x8a, 0x7f, 0x8b, 0xd8, 0x96, + 0x0b); +DEFINE_GUID(IID_IDXGIKeyedMutex, 0x9d8e1289, 0xd7b3, 0x465f, 0x81, 0x26, 0x25, 0x0e, 0x34, 0x9a, + 0xf8, 0x5d); +DEFINE_GUID(IID_IDXGISurface, 0xcafcb56c, 0x6ac3, 0x4889, 0xbf, 0x47, 0x9e, 0x23, 0xbb, 0xd2, 0x60, + 0xec); +DEFINE_GUID(IID_IDXGISurface1, 0x4AE63092, 0x6327, 0x4c1b, 0x80, 0xAE, 0xBF, 0xE1, 0x2E, 0xA3, 0x2B, + 0x86); +DEFINE_GUID(IID_IDXGIAdapter, 0x2411e7e1, 0x12ac, 0x4ccf, 0xbd, 0x14, 0x97, 0x98, 0xe8, 0x53, 0x4d, + 0xc0); +DEFINE_GUID(IID_IDXGIOutput, 0xae02eedb, 0xc735, 0x4690, 0x8d, 0x52, 0x5a, 0x8d, 0xc2, 0x02, 0x13, + 0xaa); +DEFINE_GUID(IID_IDXGISwapChain, 0x310d36a0, 0xd2e7, 0x4c0a, 0xaa, 0x04, 0x6a, 0x9d, 0x23, 0xb8, + 0x88, 0x6a); +DEFINE_GUID(IID_IDXGIFactory, 0x7b7166ec, 0x21c7, 0x44ae, 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, + 0x69); +DEFINE_GUID(IID_IDXGIDevice, 0x54ec77fa, 0x1377, 0x44e6, 0x8c, 0x32, 0x88, 0xfd, 0x5f, 0x44, 0xc8, + 0x4c); +DEFINE_GUID(IID_IDXGIFactory1, 0x770aae78, 0xf26f, 0x4dba, 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, + 0x87); +DEFINE_GUID(IID_IDXGIAdapter1, 0x29038f61, 0x3839, 0x4626, 0x91, 0xfd, 0x08, 0x68, 0x79, 0x01, 0x1a, + 0x05); +DEFINE_GUID(IID_IDXGIDevice1, 0x77db970f, 0x6276, 0x48ba, 0xba, 0x28, 0x07, 0x01, 0x43, 0xb4, 0x39, + 0x2c); + +/* dxgi1_2.h GUIDs */ + +DEFINE_GUID(IID_IDXGIDisplayControl, 0xea9dbf1a, 0xc88e, 0x4486, 0x85, 0x4a, 0x98, 0xaa, 0x01, 0x38, + 0xf3, 0x0c); +DEFINE_GUID(IID_IDXGIOutputDuplication, 0x191cfac3, 0xa341, 0x470d, 0xb2, 0x6e, 0xa8, 0x64, 0xf4, + 0x28, 0x31, 0x9c); +DEFINE_GUID(IID_IDXGISurface2, 0xaba496dd, 0xb617, 0x4cb8, 0xa8, 0x66, 0xbc, 0x44, 0xd7, 0xeb, 0x1f, + 0xa2); +DEFINE_GUID(IID_IDXGIResource1, 0x30961379, 0x4609, 0x4a41, 0x99, 0x8e, 0x54, 0xfe, 0x56, 0x7e, + 0xe0, 0xc1); +DEFINE_GUID(IID_IDXGIDevice2, 0x05008617, 0xfbfd, 0x4051, 0xa7, 0x90, 0x14, 0x48, 0x84, 0xb4, 0xf6, + 0xa9); +DEFINE_GUID(IID_IDXGISwapChain1, 0x790a45f7, 0x0d42, 0x4876, 0x98, 0x3a, 0x0a, 0x55, 0xcf, 0xe6, + 0xf4, 0xaa); +DEFINE_GUID(IID_IDXGIFactory2, 0x50c83a1c, 0xe072, 0x4c48, 0x87, 0xb0, 0x36, 0x30, 0xfa, 0x36, 0xa6, + 0xd0); +DEFINE_GUID(IID_IDXGIAdapter2, 0x0AA1AE0A, 0xFA0E, 0x4B84, 0x86, 0x44, 0xE0, 0x5F, 0xF8, 0xE5, 0xAC, + 0xB5); +DEFINE_GUID(IID_IDXGIOutput1, 0x00cddea8, 0x939b, 0x4b83, 0xa3, 0x40, 0xa6, 0x85, 0x22, 0x66, 0x66, + 0xcc); + +const char* GetDxgiErrorString(HRESULT hr) +{ + switch (hr) + { + case DXGI_STATUS_OCCLUDED: + return "DXGI_STATUS_OCCLUDED"; + case DXGI_STATUS_CLIPPED: + return "DXGI_STATUS_CLIPPED"; + case DXGI_STATUS_NO_REDIRECTION: + return "DXGI_STATUS_NO_REDIRECTION"; + case DXGI_STATUS_NO_DESKTOP_ACCESS: + return "DXGI_STATUS_NO_DESKTOP_ACCESS"; + case DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE: + return "DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE"; + case DXGI_STATUS_MODE_CHANGED: + return "DXGI_STATUS_MODE_CHANGED"; + case DXGI_STATUS_MODE_CHANGE_IN_PROGRESS: + return "DXGI_STATUS_MODE_CHANGE_IN_PROGRESS"; + case DXGI_ERROR_INVALID_CALL: + return "DXGI_ERROR_INVALID_CALL"; + case DXGI_ERROR_NOT_FOUND: + return "DXGI_ERROR_NOT_FOUND"; + case DXGI_ERROR_MORE_DATA: + return "DXGI_ERROR_MORE_DATA"; + case DXGI_ERROR_UNSUPPORTED: + return "DXGI_ERROR_UNSUPPORTED"; + case DXGI_ERROR_DEVICE_REMOVED: + return "DXGI_ERROR_DEVICE_REMOVED"; + case DXGI_ERROR_DEVICE_HUNG: + return "DXGI_ERROR_DEVICE_HUNG"; + case DXGI_ERROR_DEVICE_RESET: + return "DXGI_ERROR_DEVICE_RESET"; + case DXGI_ERROR_WAS_STILL_DRAWING: + return "DXGI_ERROR_WAS_STILL_DRAWING"; + case DXGI_ERROR_FRAME_STATISTICS_DISJOINT: + return "DXGI_ERROR_FRAME_STATISTICS_DISJOINT"; + case DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE: + return "DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE"; + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: + return "DXGI_ERROR_DRIVER_INTERNAL_ERROR"; + case DXGI_ERROR_NONEXCLUSIVE: + return "DXGI_ERROR_NONEXCLUSIVE"; + case DXGI_ERROR_NOT_CURRENTLY_AVAILABLE: + return "DXGI_ERROR_NOT_CURRENTLY_AVAILABLE"; + case DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED: + return "DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED"; + case DXGI_ERROR_REMOTE_OUTOFMEMORY: + return "DXGI_ERROR_REMOTE_OUTOFMEMORY"; + case DXGI_ERROR_ACCESS_LOST: + return "DXGI_ERROR_ACCESS_LOST"; + case DXGI_ERROR_WAIT_TIMEOUT: + return "DXGI_ERROR_WAIT_TIMEOUT"; + case DXGI_ERROR_SESSION_DISCONNECTED: + return "DXGI_ERROR_SESSION_DISCONNECTED"; + case DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE: + return "DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE"; + case DXGI_ERROR_CANNOT_PROTECT_CONTENT: + return "DXGI_ERROR_CANNOT_PROTECT_CONTENT"; + case DXGI_ERROR_ACCESS_DENIED: + return "DXGI_ERROR_ACCESS_DENIED"; + case DXGI_ERROR_NAME_ALREADY_EXISTS: + return "DXGI_ERROR_NAME_ALREADY_EXISTS"; + case DXGI_ERROR_SDK_COMPONENT_MISSING: + return "DXGI_ERROR_SDK_COMPONENT_MISSING"; + case DXGI_STATUS_UNOCCLUDED: + return "DXGI_STATUS_UNOCCLUDED"; + case DXGI_STATUS_DDA_WAS_STILL_DRAWING: + return "DXGI_STATUS_DDA_WAS_STILL_DRAWING"; + case DXGI_ERROR_MODE_CHANGE_IN_PROGRESS: + return "DXGI_ERROR_MODE_CHANGE_IN_PROGRESS"; + case DXGI_DDI_ERR_WASSTILLDRAWING: + return "DXGI_DDI_ERR_WASSTILLDRAWING"; + case DXGI_DDI_ERR_UNSUPPORTED: + return "DXGI_DDI_ERR_UNSUPPORTED"; + case DXGI_DDI_ERR_NONEXCLUSIVE: + return "DXGI_DDI_ERR_NONEXCLUSIVE"; + case 0x80070005: + return "DXGI_ERROR_ACCESS_DENIED"; + } + + return "DXGI_ERROR_UNKNOWN"; +} + +static void win_shadow_d3d11_module_init() +{ + if (d3d11_module) + return; + + d3d11_module = LoadLibraryA("d3d11.dll"); + + if (!d3d11_module) + return; + + pfnD3D11CreateDevice = (fnD3D11CreateDevice)GetProcAddress(d3d11_module, "D3D11CreateDevice"); +} + +int win_shadow_dxgi_init_duplication(winShadowSubsystem* subsystem) +{ + HRESULT hr; + UINT dTop, i = 0; + IDXGIOutput* pOutput; + DXGI_OUTPUT_DESC outputDesc = { 0 }; + DXGI_OUTPUT_DESC* pOutputDesc; + D3D11_TEXTURE2D_DESC textureDesc; + IDXGIDevice* dxgiDevice = NULL; + IDXGIAdapter* dxgiAdapter = NULL; + IDXGIOutput* dxgiOutput = NULL; + IDXGIOutput1* dxgiOutput1 = NULL; + + hr = subsystem->dxgiDevice->lpVtbl->QueryInterface(subsystem->dxgiDevice, &IID_IDXGIDevice, + (void**)&dxgiDevice); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "ID3D11Device::QueryInterface(IDXGIDevice) failure: %s (0x%08lX)", + GetDxgiErrorString(hr), hr); + return -1; + } + + hr = dxgiDevice->lpVtbl->GetParent(dxgiDevice, &IID_IDXGIAdapter, (void**)&dxgiAdapter); + + if (dxgiDevice) + { + dxgiDevice->lpVtbl->Release(dxgiDevice); + dxgiDevice = NULL; + } + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IDXGIDevice::GetParent(IDXGIAdapter) failure: %s (0x%08lX)", + GetDxgiErrorString(hr), hr); + return -1; + } + + pOutput = NULL; + + while (dxgiAdapter->lpVtbl->EnumOutputs(dxgiAdapter, i, &pOutput) != DXGI_ERROR_NOT_FOUND) + { + pOutputDesc = &outputDesc; + + hr = pOutput->lpVtbl->GetDesc(pOutput, pOutputDesc); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IDXGIOutput::GetDesc failure: %s (0x%08lX)", GetDxgiErrorString(hr), hr); + return -1; + } + + if (pOutputDesc->AttachedToDesktop) + dTop = i; + + pOutput->lpVtbl->Release(pOutput); + i++; + } + + dTop = 0; /* screen id */ + + hr = dxgiAdapter->lpVtbl->EnumOutputs(dxgiAdapter, dTop, &dxgiOutput); + + if (dxgiAdapter) + { + dxgiAdapter->lpVtbl->Release(dxgiAdapter); + dxgiAdapter = NULL; + } + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IDXGIAdapter::EnumOutputs failure: %s (0x%08lX)", GetDxgiErrorString(hr), + hr); + return -1; + } + + hr = dxgiOutput->lpVtbl->QueryInterface(dxgiOutput, &IID_IDXGIOutput1, (void**)&dxgiOutput1); + + if (dxgiOutput) + { + dxgiOutput->lpVtbl->Release(dxgiOutput); + dxgiOutput = NULL; + } + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IDXGIOutput::QueryInterface(IDXGIOutput1) failure: %s (0x%08lX)", + GetDxgiErrorString(hr), hr); + return -1; + } + + hr = dxgiOutput1->lpVtbl->DuplicateOutput(dxgiOutput1, (IUnknown*)subsystem->dxgiDevice, + &(subsystem->dxgiOutputDuplication)); + + if (dxgiOutput1) + { + dxgiOutput1->lpVtbl->Release(dxgiOutput1); + dxgiOutput1 = NULL; + } + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IDXGIOutput1::DuplicateOutput failure: %s (0x%08lX)", GetDxgiErrorString(hr), + hr); + return -1; + } + + textureDesc.Width = subsystem->width; + textureDesc.Height = subsystem->height; + textureDesc.MipLevels = 1; + textureDesc.ArraySize = 1; + textureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + textureDesc.SampleDesc.Count = 1; + textureDesc.SampleDesc.Quality = 0; + textureDesc.Usage = D3D11_USAGE_STAGING; + textureDesc.BindFlags = 0; + textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + textureDesc.MiscFlags = 0; + + hr = subsystem->dxgiDevice->lpVtbl->CreateTexture2D(subsystem->dxgiDevice, &textureDesc, NULL, + &(subsystem->dxgiStage)); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "ID3D11Device::CreateTexture2D failure: %s (0x%08lX)", GetDxgiErrorString(hr), + hr); + return -1; + } + + return 1; +} + +int win_shadow_dxgi_init(winShadowSubsystem* subsystem) +{ + UINT i = 0; + HRESULT hr; + int status; + UINT DriverTypeIndex; + IDXGIDevice* DxgiDevice = NULL; + IDXGIAdapter* DxgiAdapter = NULL; + IDXGIOutput* DxgiOutput = NULL; + IDXGIOutput1* DxgiOutput1 = NULL; + + win_shadow_d3d11_module_init(); + + if (!pfnD3D11CreateDevice) + return -1; + + for (DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex) + { + hr = pfnD3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels, + NumFeatureLevels, D3D11_SDK_VERSION, &(subsystem->dxgiDevice), + &(subsystem->featureLevel), &(subsystem->dxgiDeviceContext)); + + if (SUCCEEDED(hr)) + break; + } + + if (FAILED(hr)) + { + WLog_ERR(TAG, "D3D11CreateDevice failure: 0x%08lX", hr); + return -1; + } + + status = win_shadow_dxgi_init_duplication(subsystem); + + return status; +} + +int win_shadow_dxgi_uninit(winShadowSubsystem* subsystem) +{ + if (subsystem->dxgiStage) + { + subsystem->dxgiStage->lpVtbl->Release(subsystem->dxgiStage); + subsystem->dxgiStage = NULL; + } + + if (subsystem->dxgiDesktopImage) + { + subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage); + subsystem->dxgiDesktopImage = NULL; + } + + if (subsystem->dxgiOutputDuplication) + { + subsystem->dxgiOutputDuplication->lpVtbl->Release(subsystem->dxgiOutputDuplication); + subsystem->dxgiOutputDuplication = NULL; + } + + if (subsystem->dxgiDeviceContext) + { + subsystem->dxgiDeviceContext->lpVtbl->Release(subsystem->dxgiDeviceContext); + subsystem->dxgiDeviceContext = NULL; + } + + if (subsystem->dxgiDevice) + { + subsystem->dxgiDevice->lpVtbl->Release(subsystem->dxgiDevice); + subsystem->dxgiDevice = NULL; + } + + return 1; +} + +int win_shadow_dxgi_fetch_frame_data(winShadowSubsystem* subsystem, BYTE** ppDstData, + int* pnDstStep, int x, int y, int width, int height) +{ + int status; + HRESULT hr; + D3D11_BOX Box; + DXGI_MAPPED_RECT mappedRect; + + if ((width * height) < 1) + return 0; + + Box.top = x; + Box.left = y; + Box.right = x + width; + Box.bottom = y + height; + Box.front = 0; + Box.back = 1; + + subsystem->dxgiDeviceContext->lpVtbl->CopySubresourceRegion( + subsystem->dxgiDeviceContext, (ID3D11Resource*)subsystem->dxgiStage, 0, 0, 0, 0, + (ID3D11Resource*)subsystem->dxgiDesktopImage, 0, &Box); + + hr = subsystem->dxgiStage->lpVtbl->QueryInterface(subsystem->dxgiStage, &IID_IDXGISurface, + (void**)&(subsystem->dxgiSurface)); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "ID3D11Texture2D::QueryInterface(IDXGISurface) failure: %s 0x%08lX", + GetDxgiErrorString(hr), hr); + return -1; + } + + hr = subsystem->dxgiSurface->lpVtbl->Map(subsystem->dxgiSurface, &mappedRect, DXGI_MAP_READ); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IDXGISurface::Map failure: %s 0x%08lX", GetDxgiErrorString(hr), hr); + + if (hr == DXGI_ERROR_DEVICE_REMOVED) + { + win_shadow_dxgi_uninit(subsystem); + + status = win_shadow_dxgi_init(subsystem); + + if (status < 0) + return -1; + + return 0; + } + + return -1; + } + + subsystem->dxgiSurfaceMapped = TRUE; + + *ppDstData = mappedRect.pBits; + *pnDstStep = mappedRect.Pitch; + + return 1; +} + +int win_shadow_dxgi_release_frame_data(winShadowSubsystem* subsystem) +{ + if (subsystem->dxgiSurface) + { + if (subsystem->dxgiSurfaceMapped) + { + subsystem->dxgiSurface->lpVtbl->Unmap(subsystem->dxgiSurface); + subsystem->dxgiSurfaceMapped = FALSE; + } + + subsystem->dxgiSurface->lpVtbl->Release(subsystem->dxgiSurface); + subsystem->dxgiSurface = NULL; + } + + if (subsystem->dxgiOutputDuplication) + { + if (subsystem->dxgiFrameAcquired) + { + subsystem->dxgiOutputDuplication->lpVtbl->ReleaseFrame( + subsystem->dxgiOutputDuplication); + subsystem->dxgiFrameAcquired = FALSE; + } + } + + subsystem->pendingFrames = 0; + + return 1; +} + +int win_shadow_dxgi_get_next_frame(winShadowSubsystem* subsystem) +{ + UINT i = 0; + int status; + HRESULT hr = 0; + UINT timeout = 15; + UINT DataBufferSize = 0; + BYTE* DataBuffer = NULL; + + if (subsystem->dxgiFrameAcquired) + { + win_shadow_dxgi_release_frame_data(subsystem); + } + + if (subsystem->dxgiDesktopImage) + { + subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage); + subsystem->dxgiDesktopImage = NULL; + } + + hr = subsystem->dxgiOutputDuplication->lpVtbl->AcquireNextFrame( + subsystem->dxgiOutputDuplication, timeout, &(subsystem->dxgiFrameInfo), + &(subsystem->dxgiResource)); + + if (SUCCEEDED(hr)) + { + subsystem->dxgiFrameAcquired = TRUE; + subsystem->pendingFrames = subsystem->dxgiFrameInfo.AccumulatedFrames; + } + + if (hr == DXGI_ERROR_WAIT_TIMEOUT) + return 0; + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IDXGIOutputDuplication::AcquireNextFrame failure: %s (0x%08lX)", + GetDxgiErrorString(hr), hr); + + if (hr == DXGI_ERROR_ACCESS_LOST) + { + win_shadow_dxgi_release_frame_data(subsystem); + + if (subsystem->dxgiDesktopImage) + { + subsystem->dxgiDesktopImage->lpVtbl->Release(subsystem->dxgiDesktopImage); + subsystem->dxgiDesktopImage = NULL; + } + + if (subsystem->dxgiOutputDuplication) + { + subsystem->dxgiOutputDuplication->lpVtbl->Release(subsystem->dxgiOutputDuplication); + subsystem->dxgiOutputDuplication = NULL; + } + + status = win_shadow_dxgi_init_duplication(subsystem); + + if (status < 0) + return -1; + + return 0; + } + else if (hr == DXGI_ERROR_INVALID_CALL) + { + win_shadow_dxgi_uninit(subsystem); + + status = win_shadow_dxgi_init(subsystem); + + if (status < 0) + return -1; + + return 0; + } + + return -1; + } + + hr = subsystem->dxgiResource->lpVtbl->QueryInterface( + subsystem->dxgiResource, &IID_ID3D11Texture2D, (void**)&(subsystem->dxgiDesktopImage)); + + if (subsystem->dxgiResource) + { + subsystem->dxgiResource->lpVtbl->Release(subsystem->dxgiResource); + subsystem->dxgiResource = NULL; + } + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IDXGIResource::QueryInterface(ID3D11Texture2D) failure: %s (0x%08lX)", + GetDxgiErrorString(hr), hr); + return -1; + } + + return 1; +} + +int win_shadow_dxgi_get_invalid_region(winShadowSubsystem* subsystem) +{ + HRESULT hr; + POINT* pSrcPt; + RECT* pDstRect; + RECT* pDirtyRect; + UINT numMoveRects; + UINT numDirtyRects; + UINT UsedBufferSize; + RECTANGLE_16 invalidRect; + UINT MetadataBufferSize; + UINT MoveRectsBufferSize; + UINT DirtyRectsBufferSize; + RECT* pDirtyRectsBuffer; + DXGI_OUTDUPL_MOVE_RECT* pMoveRect; + DXGI_OUTDUPL_MOVE_RECT* pMoveRectBuffer; + rdpShadowSurface* surface = subsystem->server->surface; + + if (subsystem->dxgiFrameInfo.AccumulatedFrames == 0) + return 0; + + if (subsystem->dxgiFrameInfo.TotalMetadataBufferSize == 0) + return 0; + + MetadataBufferSize = subsystem->dxgiFrameInfo.TotalMetadataBufferSize; + + if (MetadataBufferSize > subsystem->MetadataBufferSize) + { + subsystem->MetadataBuffer = (BYTE*)realloc(subsystem->MetadataBuffer, MetadataBufferSize); + + if (!subsystem->MetadataBuffer) + return -1; + + subsystem->MetadataBufferSize = MetadataBufferSize; + } + + /* GetFrameMoveRects */ + + UsedBufferSize = 0; + + MoveRectsBufferSize = MetadataBufferSize - UsedBufferSize; + pMoveRectBuffer = (DXGI_OUTDUPL_MOVE_RECT*)&(subsystem->MetadataBuffer[UsedBufferSize]); + + hr = subsystem->dxgiOutputDuplication->lpVtbl->GetFrameMoveRects( + subsystem->dxgiOutputDuplication, MoveRectsBufferSize, pMoveRectBuffer, + &MoveRectsBufferSize); + + if (FAILED(hr)) + { + WLog_ERR(TAG, + "IDXGIOutputDuplication::GetFrameMoveRects failure: %s (0x%08lX) Size: %u Total " + "%u Used: %u", + GetDxgiErrorString(hr), hr, MoveRectsBufferSize, MetadataBufferSize, + UsedBufferSize); + return -1; + } + + /* GetFrameDirtyRects */ + + UsedBufferSize += MoveRectsBufferSize; + + DirtyRectsBufferSize = MetadataBufferSize - UsedBufferSize; + pDirtyRectsBuffer = (RECT*)&(subsystem->MetadataBuffer[UsedBufferSize]); + + hr = subsystem->dxgiOutputDuplication->lpVtbl->GetFrameDirtyRects( + subsystem->dxgiOutputDuplication, DirtyRectsBufferSize, pDirtyRectsBuffer, + &DirtyRectsBufferSize); + + if (FAILED(hr)) + { + WLog_ERR(TAG, + "IDXGIOutputDuplication::GetFrameDirtyRects failure: %s (0x%08lX) Size: %u Total " + "%u Used: %u", + GetDxgiErrorString(hr), hr, DirtyRectsBufferSize, MetadataBufferSize, + UsedBufferSize); + return -1; + } + + numMoveRects = MoveRectsBufferSize / sizeof(DXGI_OUTDUPL_MOVE_RECT); + + for (UINT i = 0; i < numMoveRects; i++) + { + pMoveRect = &pMoveRectBuffer[i]; + pSrcPt = &(pMoveRect->SourcePoint); + pDstRect = &(pMoveRect->DestinationRect); + + invalidRect.left = (UINT16)pDstRect->left; + invalidRect.top = (UINT16)pDstRect->top; + invalidRect.right = (UINT16)pDstRect->right; + invalidRect.bottom = (UINT16)pDstRect->bottom; + + region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect); + } + + numDirtyRects = DirtyRectsBufferSize / sizeof(RECT); + + for (UINT i = 0; i < numDirtyRects; i++) + { + pDirtyRect = &pDirtyRectsBuffer[i]; + + invalidRect.left = (UINT16)pDirtyRect->left; + invalidRect.top = (UINT16)pDirtyRect->top; + invalidRect.right = (UINT16)pDirtyRect->right; + invalidRect.bottom = (UINT16)pDirtyRect->bottom; + + region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect); + } + + return 1; +} + +#endif diff --git a/server/shadow/Win/win_dxgi.h b/server/shadow/Win/win_dxgi.h new file mode 100644 index 0000000..ffe80a0 --- /dev/null +++ b/server/shadow/Win/win_dxgi.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_WIN_DXGI_H +#define FREERDP_SERVER_SHADOW_WIN_DXGI_H + +#if _WIN32_WINNT >= 0x0602 +//#define WITH_DXGI_1_2 1 +#endif + +#ifdef WITH_DXGI_1_2 + +#ifndef CINTERFACE +#define CINTERFACE +#endif + +#include <D3D11.h> +#include <dxgi1_2.h> + +#endif + +#include "win_shadow.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef WITH_DXGI_1_2 + + int win_shadow_dxgi_init(winShadowSubsystem* subsystem); + int win_shadow_dxgi_uninit(winShadowSubsystem* subsystem); + + int win_shadow_dxgi_fetch_frame_data(winShadowSubsystem* subsystem, BYTE** ppDstData, + int* pnDstStep, int x, int y, int width, int height); + + int win_shadow_dxgi_get_next_frame(winShadowSubsystem* subsystem); + int win_shadow_dxgi_get_invalid_region(winShadowSubsystem* subsystem); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_WIN_DXGI_H */ diff --git a/server/shadow/Win/win_rdp.c b/server/shadow/Win/win_rdp.c new file mode 100644 index 0000000..3db073d --- /dev/null +++ b/server/shadow/Win/win_rdp.c @@ -0,0 +1,440 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/print.h> +#include <winpr/assert.h> + +#include <freerdp/log.h> + +#include "win_rdp.h" + +#define TAG SERVER_TAG("shadow.win") + +static void shw_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + shwContext* shw = (shwContext*)context; + WINPR_ASSERT(e); + WLog_INFO(TAG, "OnChannelConnected: %s", e->name); +} + +static void shw_OnChannelDisconnectedEventHandler(void* context, + const ChannelDisconnectedEventArgs* e) +{ + shwContext* shw = (shwContext*)context; + WINPR_ASSERT(e); + WLog_INFO(TAG, "OnChannelDisconnected: %s", e->name); +} + +static BOOL shw_begin_paint(rdpContext* context) +{ + shwContext* shw; + rdpGdi* gdi; + + WINPR_ASSERT(context); + gdi = context->gdi; + WINPR_ASSERT(gdi); + shw = (shwContext*)context; + gdi->primary->hdc->hwnd->invalid->null = TRUE; + gdi->primary->hdc->hwnd->ninvalid = 0; + return TRUE; +} + +static BOOL shw_end_paint(rdpContext* context) +{ + int ninvalid; + HGDI_RGN cinvalid; + RECTANGLE_16 invalidRect; + rdpGdi* gdi = context->gdi; + shwContext* shw = (shwContext*)context; + winShadowSubsystem* subsystem = shw->subsystem; + rdpShadowSurface* surface = subsystem->base.server->surface; + ninvalid = gdi->primary->hdc->hwnd->ninvalid; + cinvalid = gdi->primary->hdc->hwnd->cinvalid; + + for (int index = 0; index < ninvalid; index++) + { + invalidRect.left = cinvalid[index].x; + invalidRect.top = cinvalid[index].y; + invalidRect.right = cinvalid[index].x + cinvalid[index].w; + invalidRect.bottom = cinvalid[index].y + cinvalid[index].h; + region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect); + } + + SetEvent(subsystem->RdpUpdateEnterEvent); + WaitForSingleObject(subsystem->RdpUpdateLeaveEvent, INFINITE); + ResetEvent(subsystem->RdpUpdateLeaveEvent); + return TRUE; +} + +BOOL shw_desktop_resize(rdpContext* context) +{ + WLog_WARN(TAG, "Desktop resizing not implemented!"); + return TRUE; +} + +static BOOL shw_surface_frame_marker(rdpContext* context, + const SURFACE_FRAME_MARKER* surfaceFrameMarker) +{ + shwContext* shw = (shwContext*)context; + return TRUE; +} + +static BOOL shw_authenticate(freerdp* instance, char** username, char** password, char** domain) +{ + WLog_WARN(TAG, "Authentication not implemented, access granted to everyone!"); + return TRUE; +} + +static int shw_verify_x509_certificate(freerdp* instance, const BYTE* data, size_t length, + const char* hostname, UINT16 port, DWORD flags) +{ + WLog_WARN(TAG, "Certificate checks not implemented, access granted to everyone!"); + return 1; +} + +static void shw_OnConnectionResultEventHandler(void* context, const ConnectionResultEventArgs* e) +{ + shwContext* shw = (shwContext*)context; + WINPR_ASSERT(e); + WLog_INFO(TAG, "OnConnectionResult: %d", e->result); +} + +static BOOL shw_pre_connect(freerdp* instance) +{ + shwContext* shw; + rdpContext* context = instance->context; + shw = (shwContext*)context; + PubSub_SubscribeConnectionResult(context->pubSub, shw_OnConnectionResultEventHandler); + PubSub_SubscribeChannelConnected(context->pubSub, shw_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(context->pubSub, shw_OnChannelDisconnectedEventHandler); + + return TRUE; +} + +static BOOL shw_post_connect(freerdp* instance) +{ + rdpGdi* gdi; + shwContext* shw; + rdpUpdate* update; + rdpSettings* settings; + + WINPR_ASSERT(instance); + + shw = (shwContext*)instance->context; + WINPR_ASSERT(shw); + + update = instance->context->update; + WINPR_ASSERT(update); + + settings = instance->context->settings; + WINPR_ASSERT(settings); + + if (!gdi_init(instance, PIXEL_FORMAT_BGRX32)) + return FALSE; + + gdi = instance->context->gdi; + update->BeginPaint = shw_begin_paint; + update->EndPaint = shw_end_paint; + update->DesktopResize = shw_desktop_resize; + update->SurfaceFrameMarker = shw_surface_frame_marker; + return TRUE; +} + +static DWORD WINAPI shw_client_thread(LPVOID arg) +{ + int index; + BOOL bSuccess; + shwContext* shw; + rdpContext* context; + rdpChannels* channels; + + freerdp* instance = (freerdp*)arg; + WINPR_ASSERT(instance); + + context = (rdpContext*)instance->context; + WINPR_ASSERT(context); + + shw = (shwContext*)context; + + bSuccess = freerdp_connect(instance); + WLog_INFO(TAG, "freerdp_connect: %d", bSuccess); + + if (!bSuccess) + { + ExitThread(0); + return 0; + } + + channels = context->channels; + WINPR_ASSERT(channels); + + while (1) + { + DWORD status; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD count = freerdp_get_event_handles(instance->context, handles, ARRAYSIZE(handles)); + + if ((count == 0) || (count == MAXIMUM_WAIT_OBJECTS)) + { + WLog_ERR(TAG, "Failed to get FreeRDP event handles"); + break; + } + + handles[count++] = freerdp_channels_get_event_handle(instance); + + if (MsgWaitForMultipleObjects(count, handles, FALSE, 1000, QS_ALLINPUT) == WAIT_FAILED) + { + WLog_ERR(TAG, "MsgWaitForMultipleObjects failure: 0x%08lX", GetLastError()); + break; + } + + if (!freerdp_check_fds(instance)) + { + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + break; + } + + if (freerdp_shall_disconnect_context(instance->context)) + { + break; + } + + if (!freerdp_channels_check_fds(channels, instance)) + { + WLog_ERR(TAG, "Failed to check channels file descriptor"); + break; + } + } + + freerdp_free(instance); + ExitThread(0); + return 0; +} + +/** + * Client Interface + */ + +static BOOL shw_freerdp_client_global_init(void) +{ + return TRUE; +} + +static void shw_freerdp_client_global_uninit(void) +{ +} + +static int shw_freerdp_client_start(rdpContext* context) +{ + shwContext* shw; + freerdp* instance = context->instance; + shw = (shwContext*)context; + + if (!(shw->common.thread = CreateThread(NULL, 0, shw_client_thread, instance, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create thread"); + return -1; + } + + return 0; +} + +static int shw_freerdp_client_stop(rdpContext* context) +{ + shwContext* shw = (shwContext*)context; + SetEvent(shw->StopEvent); + return 0; +} + +static BOOL shw_freerdp_client_new(freerdp* instance, rdpContext* context) +{ + shwContext* shw; + rdpSettings* settings; + + WINPR_ASSERT(instance); + WINPR_ASSERT(context); + + shw = (shwContext*)instance->context; + WINPR_ASSERT(shw); + + if (!(shw->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + return FALSE; + + instance->LoadChannels = freerdp_client_load_channels; + instance->PreConnect = shw_pre_connect; + instance->PostConnect = shw_post_connect; + instance->Authenticate = shw_authenticate; + instance->VerifyX509Certificate = shw_verify_x509_certificate; + + settings = context->settings; + WINPR_ASSERT(settings); + + shw->settings = settings; + if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncChannels, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncUpdate, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_ExternalCertificateManagement, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_OffscreenSupportLevel, FALSE)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GLYPH_SUPPORT_NONE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_BrushSupportLevel, FALSE)) + return FALSE; + ZeroMemory(freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport), 32); + if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_AltSecFrameMarkerSupport, TRUE)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathInput, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathOutput, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_LargePointerFlag, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportHeartbeatPdu, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMultitransport, FALSE)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, CONNECTION_TYPE_LAN)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE)) + return FALSE; + return TRUE; +} + +static void shw_freerdp_client_free(freerdp* instance, rdpContext* context) +{ + shwContext* shw = (shwContext*)instance->context; +} + +int shw_RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + pEntryPoints->Version = 1; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->settings = NULL; + pEntryPoints->ContextSize = sizeof(shwContext); + pEntryPoints->GlobalInit = shw_freerdp_client_global_init; + pEntryPoints->GlobalUninit = shw_freerdp_client_global_uninit; + pEntryPoints->ClientNew = shw_freerdp_client_new; + pEntryPoints->ClientFree = shw_freerdp_client_free; + pEntryPoints->ClientStart = shw_freerdp_client_start; + pEntryPoints->ClientStop = shw_freerdp_client_stop; + return 0; +} + +int win_shadow_rdp_init(winShadowSubsystem* subsystem) +{ + rdpContext* context; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints = { 0 }; + + clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS); + clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION; + shw_RdpClientEntry(&clientEntryPoints); + + if (!(subsystem->RdpUpdateEnterEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto fail_enter_event; + + if (!(subsystem->RdpUpdateLeaveEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto fail_leave_event; + + if (!(context = freerdp_client_context_new(&clientEntryPoints))) + goto fail_context; + + subsystem->shw = (shwContext*)context; + subsystem->shw->settings = context->settings; + subsystem->shw->subsystem = subsystem; + return 1; +fail_context: + CloseHandle(subsystem->RdpUpdateLeaveEvent); +fail_leave_event: + CloseHandle(subsystem->RdpUpdateEnterEvent); +fail_enter_event: + return -1; +} + +int win_shadow_rdp_start(winShadowSubsystem* subsystem) +{ + int status; + shwContext* shw = subsystem->shw; + rdpContext* context = (rdpContext*)shw; + status = freerdp_client_start(context); + return status; +} + +int win_shadow_rdp_stop(winShadowSubsystem* subsystem) +{ + int status; + shwContext* shw = subsystem->shw; + rdpContext* context = (rdpContext*)shw; + status = freerdp_client_stop(context); + return status; +} + +int win_shadow_rdp_uninit(winShadowSubsystem* subsystem) +{ + win_shadow_rdp_stop(subsystem); + return 1; +} diff --git a/server/shadow/Win/win_rdp.h b/server/shadow/Win/win_rdp.h new file mode 100644 index 0000000..07dbf3b --- /dev/null +++ b/server/shadow/Win/win_rdp.h @@ -0,0 +1,56 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_WIN_RDP_H +#define FREERDP_SERVER_SHADOW_WIN_RDP_H + +#include <freerdp/addin.h> +#include <freerdp/gdi/gdi.h> +#include <freerdp/client/cmdline.h> +#include <freerdp/channels/channels.h> + +typedef struct shw_context shwContext; + +#include "win_shadow.h" + +struct shw_context +{ + rdpClientContext common; + + HANDLE StopEvent; + freerdp* instance; + rdpSettings* settings; + winShadowSubsystem* subsystem; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + + int win_shadow_rdp_init(winShadowSubsystem* subsystem); + int win_shadow_rdp_uninit(winShadowSubsystem* subsystem); + + int win_shadow_rdp_start(winShadowSubsystem* subsystem); + int win_shadow_rdp_stop(winShadowSubsystem* subsystem); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_WIN_RDP_H */ diff --git a/server/shadow/Win/win_shadow.c b/server/shadow/Win/win_shadow.c new file mode 100644 index 0000000..d3d5a17 --- /dev/null +++ b/server/shadow/Win/win_shadow.c @@ -0,0 +1,560 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <windows.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/sysinfo.h> + +#include <freerdp/log.h> +#include <freerdp/codec/color.h> +#include <freerdp/codec/region.h> +#include <freerdp/server/server-common.h> + +#include "win_shadow.h" + +#define TAG SERVER_TAG("shadow.win") + +/* https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event + * does not mention this flag is only supported if building for _WIN32_WINNT >= 0x0600 + */ +#ifndef MOUSEEVENTF_HWHEEL +#define MOUSEEVENTF_HWHEEL 0x1000 +#endif + +static BOOL win_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT32 flags) +{ + WLog_WARN(TAG, "TODO: Implement!"); + return TRUE; +} + +static BOOL win_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client, + UINT16 flags, UINT8 code) +{ + UINT rc; + INPUT event; + event.type = INPUT_KEYBOARD; + event.ki.wVk = 0; + event.ki.wScan = code; + event.ki.dwFlags = KEYEVENTF_SCANCODE; + event.ki.dwExtraInfo = 0; + event.ki.time = 0; + + if (flags & KBD_FLAGS_RELEASE) + event.ki.dwFlags |= KEYEVENTF_KEYUP; + + if (flags & KBD_FLAGS_EXTENDED) + event.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + + rc = SendInput(1, &event, sizeof(INPUT)); + if (rc == 0) + return FALSE; + return TRUE; +} + +static BOOL win_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT16 flags, + UINT16 code) +{ + UINT rc; + INPUT event; + event.type = INPUT_KEYBOARD; + event.ki.wVk = 0; + event.ki.wScan = code; + event.ki.dwFlags = KEYEVENTF_UNICODE; + event.ki.dwExtraInfo = 0; + event.ki.time = 0; + + if (flags & KBD_FLAGS_RELEASE) + event.ki.dwFlags |= KEYEVENTF_KEYUP; + + rc = SendInput(1, &event, sizeof(INPUT)); + if (rc == 0) + return FALSE; + return TRUE; +} + +static BOOL win_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client, + UINT16 flags, UINT16 x, UINT16 y) +{ + UINT rc = 1; + INPUT event = { 0 }; + float width; + float height; + + event.type = INPUT_MOUSE; + + if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL)) + { + if (flags & PTR_FLAGS_WHEEL) + event.mi.dwFlags = MOUSEEVENTF_WHEEL; + else + event.mi.dwFlags = MOUSEEVENTF_HWHEEL; + event.mi.mouseData = flags & WheelRotationMask; + + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + event.mi.mouseData *= -1; + + rc = SendInput(1, &event, sizeof(INPUT)); + + /* The build target is a system that did not support MOUSEEVENTF_HWHEEL + * but it may run on newer systems supporting it. + * Ignore the return value in these cases. + */ +#if (_WIN32_WINNT < 0x0600) + if (flags & PTR_FLAGS_HWHEEL) + rc = 1; +#endif + } + else + { + width = (float)GetSystemMetrics(SM_CXSCREEN); + height = (float)GetSystemMetrics(SM_CYSCREEN); + event.mi.dx = (LONG)((float)x * (65535.0f / width)); + event.mi.dy = (LONG)((float)y * (65535.0f / height)); + event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE; + + if (flags & PTR_FLAGS_MOVE) + { + event.mi.dwFlags |= MOUSEEVENTF_MOVE; + rc = SendInput(1, &event, sizeof(INPUT)); + if (rc == 0) + return FALSE; + } + + event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE; + + if (flags & PTR_FLAGS_BUTTON1) + { + if (flags & PTR_FLAGS_DOWN) + event.mi.dwFlags |= MOUSEEVENTF_LEFTDOWN; + else + event.mi.dwFlags |= MOUSEEVENTF_LEFTUP; + + rc = SendInput(1, &event, sizeof(INPUT)); + } + else if (flags & PTR_FLAGS_BUTTON2) + { + if (flags & PTR_FLAGS_DOWN) + event.mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN; + else + event.mi.dwFlags |= MOUSEEVENTF_RIGHTUP; + + rc = SendInput(1, &event, sizeof(INPUT)); + } + else if (flags & PTR_FLAGS_BUTTON3) + { + if (flags & PTR_FLAGS_DOWN) + event.mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN; + else + event.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; + + rc = SendInput(1, &event, sizeof(INPUT)); + } + } + + if (rc == 0) + return FALSE; + return TRUE; +} + +static BOOL win_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT16 flags, UINT16 x, + UINT16 y) +{ + UINT rc = 1; + INPUT event = { 0 }; + float width; + float height; + + if ((flags & PTR_XFLAGS_BUTTON1) || (flags & PTR_XFLAGS_BUTTON2)) + { + event.type = INPUT_MOUSE; + + if (flags & PTR_FLAGS_MOVE) + { + width = (float)GetSystemMetrics(SM_CXSCREEN); + height = (float)GetSystemMetrics(SM_CYSCREEN); + event.mi.dx = (LONG)((float)x * (65535.0f / width)); + event.mi.dy = (LONG)((float)y * (65535.0f / height)); + event.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + rc = SendInput(1, &event, sizeof(INPUT)); + if (rc == 0) + return FALSE; + } + + event.mi.dx = event.mi.dy = event.mi.dwFlags = 0; + + if (flags & PTR_XFLAGS_DOWN) + event.mi.dwFlags |= MOUSEEVENTF_XDOWN; + else + event.mi.dwFlags |= MOUSEEVENTF_XUP; + + if (flags & PTR_XFLAGS_BUTTON1) + event.mi.mouseData = XBUTTON1; + else if (flags & PTR_XFLAGS_BUTTON2) + event.mi.mouseData = XBUTTON2; + + rc = SendInput(1, &event, sizeof(INPUT)); + } + + if (rc == 0) + return FALSE; + return TRUE; +} + +static int win_shadow_invalidate_region(winShadowSubsystem* subsystem, int x, int y, int width, + int height) +{ + rdpShadowServer* server; + rdpShadowSurface* surface; + RECTANGLE_16 invalidRect; + server = subsystem->base.server; + surface = server->surface; + invalidRect.left = x; + invalidRect.top = y; + invalidRect.right = x + width; + invalidRect.bottom = y + height; + EnterCriticalSection(&(surface->lock)); + region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect); + LeaveCriticalSection(&(surface->lock)); + return 1; +} + +static int win_shadow_surface_copy(winShadowSubsystem* subsystem) +{ + int x, y; + int width; + int height; + int count; + int status = 1; + int nDstStep = 0; + DWORD DstFormat; + BYTE* pDstData = NULL; + rdpShadowServer* server; + rdpShadowSurface* surface; + RECTANGLE_16 surfaceRect; + RECTANGLE_16 invalidRect; + const RECTANGLE_16* extents; + server = subsystem->base.server; + surface = server->surface; + + if (ArrayList_Count(server->clients) < 1) + return 1; + + surfaceRect.left = surface->x; + surfaceRect.top = surface->y; + surfaceRect.right = surface->x + surface->width; + surfaceRect.bottom = surface->y + surface->height; + region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect); + + if (region16_is_empty(&(surface->invalidRegion))) + return 1; + + extents = region16_extents(&(surface->invalidRegion)); + CopyMemory(&invalidRect, extents, sizeof(RECTANGLE_16)); + shadow_capture_align_clip_rect(&invalidRect, &surfaceRect); + x = invalidRect.left; + y = invalidRect.top; + width = invalidRect.right - invalidRect.left; + height = invalidRect.bottom - invalidRect.top; + + if (0) + { + x = 0; + y = 0; + width = surface->width; + height = surface->height; + } + + WLog_INFO(TAG, "SurfaceCopy x: %d y: %d width: %d height: %d right: %d bottom: %d", x, y, width, + height, x + width, y + height); +#if defined(WITH_WDS_API) + { + rdpGdi* gdi; + shwContext* shw; + rdpContext* context; + + WINPR_ASSERT(subsystem); + shw = subsystem->shw; + WINPR_ASSERT(shw); + + context = &shw->common.context; + WINPR_ASSERT(context); + + gdi = context->gdi; + WINPR_ASSERT(gdi); + + pDstData = gdi->primary_buffer; + nDstStep = gdi->width * 4; + DstFormat = gdi->dstFormat; + } +#elif defined(WITH_DXGI_1_2) + DstFormat = PIXEL_FORMAT_BGRX32; + status = win_shadow_dxgi_fetch_frame_data(subsystem, &pDstData, &nDstStep, x, y, width, height); +#endif + + if (status <= 0) + return status; + + if (!freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y, width, height, + pDstData, DstFormat, nDstStep, x, y, NULL, FREERDP_FLIP_NONE)) + return ERROR_INTERNAL_ERROR; + + ArrayList_Lock(server->clients); + count = ArrayList_Count(server->clients); + shadow_subsystem_frame_update(&subsystem->base); + ArrayList_Unlock(server->clients); + region16_clear(&(surface->invalidRegion)); + return 1; +} + +#if defined(WITH_WDS_API) + +static DWORD WINAPI win_shadow_subsystem_thread(LPVOID arg) +{ + winShadowSubsystem* subsystem = (winShadowSubsystem*)arg; + DWORD status; + DWORD nCount; + HANDLE events[32]; + HANDLE StopEvent; + StopEvent = subsystem->base.server->StopEvent; + nCount = 0; + events[nCount++] = StopEvent; + events[nCount++] = subsystem->RdpUpdateEnterEvent; + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (WaitForSingleObject(StopEvent, 0) == WAIT_OBJECT_0) + { + break; + } + + if (WaitForSingleObject(subsystem->RdpUpdateEnterEvent, 0) == WAIT_OBJECT_0) + { + win_shadow_surface_copy(subsystem); + ResetEvent(subsystem->RdpUpdateEnterEvent); + SetEvent(subsystem->RdpUpdateLeaveEvent); + } + } + + ExitThread(0); + return 0; +} + +#elif defined(WITH_DXGI_1_2) + +static DWORD WINAPI win_shadow_subsystem_thread(LPVOID arg) +{ + winShadowSubsystem* subsystem = (winShadowSubsystem*)arg; + int fps; + DWORD status; + DWORD nCount; + UINT64 cTime; + DWORD dwTimeout; + DWORD dwInterval; + UINT64 frameTime; + HANDLE events[32]; + HANDLE StopEvent; + StopEvent = subsystem->server->StopEvent; + nCount = 0; + events[nCount++] = StopEvent; + fps = 16; + dwInterval = 1000 / fps; + frameTime = GetTickCount64() + dwInterval; + + while (1) + { + dwTimeout = INFINITE; + cTime = GetTickCount64(); + dwTimeout = (DWORD)((cTime > frameTime) ? 0 : frameTime - cTime); + status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout); + + if (WaitForSingleObject(StopEvent, 0) == WAIT_OBJECT_0) + { + break; + } + + if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime)) + { + int dxgi_status; + dxgi_status = win_shadow_dxgi_get_next_frame(subsystem); + + if (dxgi_status > 0) + dxgi_status = win_shadow_dxgi_get_invalid_region(subsystem); + + if (dxgi_status > 0) + win_shadow_surface_copy(subsystem); + + dwInterval = 1000 / fps; + frameTime += dwInterval; + } + } + + ExitThread(0); + return 0; +} + +#endif + +static UINT32 win_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors) +{ + HDC hdc; + int index; + int desktopWidth; + int desktopHeight; + DWORD iDevNum = 0; + int numMonitors = 0; + MONITOR_DEF* monitor; + DISPLAY_DEVICE displayDevice = { 0 }; + + displayDevice.cb = sizeof(DISPLAY_DEVICE); + + if (EnumDisplayDevices(NULL, iDevNum, &displayDevice, 0)) + { + hdc = CreateDC(displayDevice.DeviceName, NULL, NULL, NULL); + desktopWidth = GetDeviceCaps(hdc, HORZRES); + desktopHeight = GetDeviceCaps(hdc, VERTRES); + index = 0; + numMonitors = 1; + monitor = &monitors[index]; + monitor->left = 0; + monitor->top = 0; + monitor->right = desktopWidth; + monitor->bottom = desktopHeight; + monitor->flags = 1; + DeleteDC(hdc); + } + + return numMonitors; +} + +static int win_shadow_subsystem_init(rdpShadowSubsystem* arg) +{ + winShadowSubsystem* subsystem = (winShadowSubsystem*)arg; + int status; + MONITOR_DEF* virtualScreen; + subsystem->base.numMonitors = win_shadow_enum_monitors(subsystem->base.monitors, 16); +#if defined(WITH_WDS_API) + status = win_shadow_wds_init(subsystem); +#elif defined(WITH_DXGI_1_2) + status = win_shadow_dxgi_init(subsystem); +#endif + virtualScreen = &(subsystem->base.virtualScreen); + virtualScreen->left = 0; + virtualScreen->top = 0; + virtualScreen->right = subsystem->width; + virtualScreen->bottom = subsystem->height; + virtualScreen->flags = 1; + WLog_INFO(TAG, "width: %d height: %d", subsystem->width, subsystem->height); + return 1; +} + +static int win_shadow_subsystem_uninit(rdpShadowSubsystem* arg) +{ + winShadowSubsystem* subsystem = (winShadowSubsystem*)arg; + + if (!subsystem) + return -1; + +#if defined(WITH_WDS_API) + win_shadow_wds_uninit(subsystem); +#elif defined(WITH_DXGI_1_2) + win_shadow_dxgi_uninit(subsystem); +#endif + return 1; +} + +static int win_shadow_subsystem_start(rdpShadowSubsystem* arg) +{ + winShadowSubsystem* subsystem = (winShadowSubsystem*)arg; + HANDLE thread; + + if (!subsystem) + return -1; + + if (!(thread = CreateThread(NULL, 0, win_shadow_subsystem_thread, (void*)subsystem, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create thread"); + return -1; + } + + return 1; +} + +static int win_shadow_subsystem_stop(rdpShadowSubsystem* arg) +{ + winShadowSubsystem* subsystem = (winShadowSubsystem*)arg; + + if (!subsystem) + return -1; + + return 1; +} + +static void win_shadow_subsystem_free(rdpShadowSubsystem* arg) +{ + winShadowSubsystem* subsystem = (winShadowSubsystem*)arg; + + if (!subsystem) + return; + + win_shadow_subsystem_uninit(arg); + free(subsystem); +} + +static rdpShadowSubsystem* win_shadow_subsystem_new(void) +{ + winShadowSubsystem* subsystem; + subsystem = (winShadowSubsystem*)calloc(1, sizeof(winShadowSubsystem)); + + if (!subsystem) + return NULL; + + subsystem->base.SynchronizeEvent = win_shadow_input_synchronize_event; + subsystem->base.KeyboardEvent = win_shadow_input_keyboard_event; + subsystem->base.UnicodeKeyboardEvent = win_shadow_input_unicode_keyboard_event; + subsystem->base.MouseEvent = win_shadow_input_mouse_event; + subsystem->base.ExtendedMouseEvent = win_shadow_input_extended_mouse_event; + return &subsystem->base; +} + +FREERDP_API const char* ShadowSubsystemName(void) +{ + return "Win"; +} + +FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints) +{ + const char name[] = "windows shadow subsystem"; + const char* arg[] = { name }; + + freerdp_server_warn_unmaintained(ARRAYSIZE(arg), arg); + pEntryPoints->New = win_shadow_subsystem_new; + pEntryPoints->Free = win_shadow_subsystem_free; + pEntryPoints->Init = win_shadow_subsystem_init; + pEntryPoints->Uninit = win_shadow_subsystem_uninit; + pEntryPoints->Start = win_shadow_subsystem_start; + pEntryPoints->Stop = win_shadow_subsystem_stop; + pEntryPoints->EnumMonitors = win_shadow_enum_monitors; + return 1; +} diff --git a/server/shadow/Win/win_shadow.h b/server/shadow/Win/win_shadow.h new file mode 100644 index 0000000..835bb66 --- /dev/null +++ b/server/shadow/Win/win_shadow.h @@ -0,0 +1,89 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_WIN_H +#define FREERDP_SERVER_SHADOW_WIN_H + +#include <freerdp/assistance.h> + +#include <freerdp/server/shadow.h> + +typedef struct win_shadow_subsystem winShadowSubsystem; + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/stream.h> +#include <winpr/collections.h> + +#include "win_rdp.h" +#include "win_wds.h" +#include "win_dxgi.h" + +struct win_shadow_subsystem +{ + rdpShadowSubsystem base; + + int bpp; + int width; + int height; + +#ifdef WITH_WDS_API + HWND hWnd; + shwContext* shw; + HANDLE RdpUpdateEnterEvent; + HANDLE RdpUpdateLeaveEvent; + rdpAssistanceFile* pAssistanceFile; + _IRDPSessionEvents* pSessionEvents; + IRDPSRAPISharingSession* pSharingSession; + IRDPSRAPIInvitation* pInvitation; + IRDPSRAPIInvitationManager* pInvitationMgr; + IRDPSRAPISessionProperties* pSessionProperties; + IRDPSRAPIVirtualChannelManager* pVirtualChannelMgr; + IRDPSRAPIApplicationFilter* pApplicationFilter; + IRDPSRAPIAttendeeManager* pAttendeeMgr; +#endif + +#ifdef WITH_DXGI_1_2 + UINT pendingFrames; + BYTE* MetadataBuffer; + UINT MetadataBufferSize; + BOOL dxgiSurfaceMapped; + BOOL dxgiFrameAcquired; + ID3D11Device* dxgiDevice; + IDXGISurface* dxgiSurface; + ID3D11Texture2D* dxgiStage; + IDXGIResource* dxgiResource; + D3D_FEATURE_LEVEL featureLevel; + ID3D11Texture2D* dxgiDesktopImage; + DXGI_OUTDUPL_FRAME_INFO dxgiFrameInfo; + ID3D11DeviceContext* dxgiDeviceContext; + IDXGIOutputDuplication* dxgiOutputDuplication; +#endif +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_WIN_H */ diff --git a/server/shadow/Win/win_wds.c b/server/shadow/Win/win_wds.c new file mode 100644 index 0000000..197e61f --- /dev/null +++ b/server/shadow/Win/win_wds.c @@ -0,0 +1,850 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <freerdp/log.h> + +#include "win_rdp.h" + +#include "win_wds.h" + +/** + * Windows Desktop Sharing API: + * http://blogs.msdn.com/b/rds/archive/2007/03/08/windows-desktop-sharing-api.aspx + * + * Windows Desktop Sharing Interfaces: + * http://msdn.microsoft.com/en-us/library/aa373871%28v=vs.85%29.aspx + * + * Offer Remote Assistance Sample C: + * http://msdn.microsoft.com/en-us/library/ms811079.aspx#remoteassistanceapi_topic2b + * + * Remote Assistance in XP: Programmatically establish an RDP session: + * http://www.codeproject.com/Articles/29939/Remote-Assistance-in-XP-Programmatically-establish + */ + +#undef DEFINE_GUID +#define INITGUID + +#include <initguid.h> + +#include <freerdp/assistance.h> + +#define TAG SERVER_TAG("shadow.win") + +DEFINE_GUID(CLSID_RDPSession, 0x9B78F0E6, 0x3E05, 0x4A5B, 0xB2, 0xE8, 0xE7, 0x43, 0xA8, 0x95, 0x6B, + 0x65); +DEFINE_GUID(DIID__IRDPSessionEvents, 0x98a97042, 0x6698, 0x40e9, 0x8e, 0xfd, 0xb3, 0x20, 0x09, 0x90, + 0x00, 0x4b); +DEFINE_GUID(IID_IRDPSRAPISharingSession, 0xeeb20886, 0xe470, 0x4cf6, 0x84, 0x2b, 0x27, 0x39, 0xc0, + 0xec, 0x5c, 0xfb); +DEFINE_GUID(IID_IRDPSRAPIAttendee, 0xec0671b3, 0x1b78, 0x4b80, 0xa4, 0x64, 0x91, 0x32, 0x24, 0x75, + 0x43, 0xe3); +DEFINE_GUID(IID_IRDPSRAPIAttendeeManager, 0xba3a37e8, 0x33da, 0x4749, 0x8d, 0xa0, 0x07, 0xfa, 0x34, + 0xda, 0x79, 0x44); +DEFINE_GUID(IID_IRDPSRAPISessionProperties, 0x339b24f2, 0x9bc0, 0x4f16, 0x9a, 0xac, 0xf1, 0x65, + 0x43, 0x3d, 0x13, 0xd4); +DEFINE_GUID(CLSID_RDPSRAPIApplicationFilter, 0xe35ace89, 0xc7e8, 0x427e, 0xa4, 0xf9, 0xb9, 0xda, + 0x07, 0x28, 0x26, 0xbd); +DEFINE_GUID(CLSID_RDPSRAPIInvitationManager, 0x53d9c9db, 0x75ab, 0x4271, 0x94, 0x8a, 0x4c, 0x4e, + 0xb3, 0x6a, 0x8f, 0x2b); + +static ULONG Shadow_IRDPSessionEvents_RefCount = 0; + +const char* GetRDPSessionEventString(DISPID id) +{ + switch (id) + { + case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_CONNECTED: + return "OnAttendeeConnected"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_DISCONNECTED: + return "OnAttendeeDisconnected"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_UPDATE: + return "OnAttendeeUpdate"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_ERROR: + return "OnError"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTED: + return "OnConnectionEstablished"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIEWER_DISCONNECTED: + return "OnConnectionTerminated"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIEWER_AUTHENTICATED: + return "OnConnectionAuthenticated"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTFAILED: + return "OnConnectionFailed"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_CTRLLEVEL_CHANGE_REQUEST: + return "OnControlLevelChangeRequest"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_PAUSED: + return "OnGraphicsStreamPaused"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_RESUMED: + return "OnGraphicsStreamResumed"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_JOIN: + return "OnChannelJoin"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_LEAVE: + return "OnChannelLeave"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_DATARECEIVED: + return "OnChannelDataReceived"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_SENDCOMPLETED: + return "OnChannelDataSent"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_OPEN: + return "OnApplicationOpen"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_CLOSE: + return "OnApplicationClose"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_UPDATE: + return "OnApplicationUpdate"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_WINDOW_OPEN: + return "OnWindowOpen"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_WINDOW_CLOSE: + return "OnWindowClose"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_WINDOW_UPDATE: + return "OnWindowUpdate"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_APPFILTER_UPDATE: + return "OnAppFilterUpdate"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_SHARED_RECT_CHANGED: + return "OnSharedRectChanged"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_FOCUSRELEASED: + return "OnFocusReleased"; + break; + + case DISPID_RDPSRAPI_EVENT_ON_SHARED_DESKTOP_SETTINGS_CHANGED: + return "OnSharedDesktopSettingsChanged"; + break; + + case DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED: + return "OnViewingSizeChanged"; + break; + } + + return "OnUnknown"; +} + +static HRESULT STDMETHODCALLTYPE +Shadow_IRDPSessionEvents_QueryInterface(__RPC__in _IRDPSessionEvents* This, + /* [in] */ __RPC__in REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void** ppvObject) +{ + *ppvObject = NULL; + + if (IsEqualIID(riid, &DIID__IRDPSessionEvents) || IsEqualIID(riid, &IID_IDispatch) || + IsEqualIID(riid, &IID_IUnknown)) + { + *ppvObject = This; + } + + if (!(*ppvObject)) + return E_NOINTERFACE; + + This->lpVtbl->AddRef(This); + return S_OK; +} + +static ULONG STDMETHODCALLTYPE Shadow_IRDPSessionEvents_AddRef(__RPC__in _IRDPSessionEvents* This) +{ + Shadow_IRDPSessionEvents_RefCount++; + return Shadow_IRDPSessionEvents_RefCount; +} + +static ULONG STDMETHODCALLTYPE Shadow_IRDPSessionEvents_Release(__RPC__in _IRDPSessionEvents* This) +{ + if (!Shadow_IRDPSessionEvents_RefCount) + return 0; + + Shadow_IRDPSessionEvents_RefCount--; + return Shadow_IRDPSessionEvents_RefCount; +} + +static HRESULT STDMETHODCALLTYPE +Shadow_IRDPSessionEvents_GetTypeInfoCount(__RPC__in _IRDPSessionEvents* This, + /* [out] */ __RPC__out UINT* pctinfo) +{ + WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetTypeInfoCount"); + *pctinfo = 1; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +Shadow_IRDPSessionEvents_GetTypeInfo(__RPC__in _IRDPSessionEvents* This, + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ __RPC__deref_out_opt ITypeInfo** ppTInfo) +{ + WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetTypeInfo"); + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE Shadow_IRDPSessionEvents_GetIDsOfNames( + __RPC__in _IRDPSessionEvents* This, + /* [in] */ __RPC__in REFIID riid, + /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR* rgszNames, + /* [range][in] */ __RPC__in_range(0, 16384) UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID* rgDispId) +{ + WLog_INFO(TAG, "Shadow_IRDPSessionEvents_GetIDsOfNames"); + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE Shadow_IRDPSessionEvents_Invoke(_IRDPSessionEvents* This, + /* [annotation][in] */ + _In_ DISPID dispIdMember, + /* [annotation][in] */ + _In_ REFIID riid, + /* [annotation][in] */ + _In_ LCID lcid, + /* [annotation][in] */ + _In_ WORD wFlags, + /* [annotation][out][in] */ + _In_ DISPPARAMS* pDispParams, + /* [annotation][out] */ + _Out_opt_ VARIANT* pVarResult, + /* [annotation][out] */ + _Out_opt_ EXCEPINFO* pExcepInfo, + /* [annotation][out] */ + _Out_opt_ UINT* puArgErr) +{ + HRESULT hr; + VARIANT vr; + UINT uArgErr; + WLog_INFO(TAG, "%s (%ld)", GetRDPSessionEventString(dispIdMember), dispIdMember); + + switch (dispIdMember) + { + case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_CONNECTED: + { + int level; + IDispatch* pDispatch; + IRDPSRAPIAttendee* pAttendee; + vr.vt = VT_DISPATCH; + vr.pdispVal = NULL; + hr = DispGetParam(pDispParams, 0, VT_DISPATCH, &vr, &uArgErr); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "%s DispGetParam(0, VT_DISPATCH) failure: 0x%08lX", + GetRDPSessionEventString(dispIdMember), hr); + return hr; + } + + pDispatch = vr.pdispVal; + hr = pDispatch->lpVtbl->QueryInterface(pDispatch, &IID_IRDPSRAPIAttendee, + (void**)&pAttendee); + + if (FAILED(hr)) + { + WLog_INFO(TAG, "%s IDispatch::QueryInterface(IRDPSRAPIAttendee) failure: 0x%08lX", + GetRDPSessionEventString(dispIdMember), hr); + return hr; + } + + level = CTRL_LEVEL_VIEW; + // level = CTRL_LEVEL_INTERACTIVE; + hr = pAttendee->lpVtbl->put_ControlLevel(pAttendee, level); + + if (FAILED(hr)) + { + WLog_INFO(TAG, "%s IRDPSRAPIAttendee::put_ControlLevel() failure: 0x%08lX", + GetRDPSessionEventString(dispIdMember), hr); + return hr; + } + + pAttendee->lpVtbl->Release(pAttendee); + } + break; + + case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_DISCONNECTED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_ATTENDEE_UPDATE: + break; + + case DISPID_RDPSRAPI_EVENT_ON_ERROR: + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIEWER_DISCONNECTED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIEWER_AUTHENTICATED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIEWER_CONNECTFAILED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_CTRLLEVEL_CHANGE_REQUEST: + { + int level; + IDispatch* pDispatch; + IRDPSRAPIAttendee* pAttendee; + vr.vt = VT_INT; + vr.pdispVal = NULL; + hr = DispGetParam(pDispParams, 1, VT_INT, &vr, &uArgErr); + + if (FAILED(hr)) + { + WLog_INFO(TAG, "%s DispGetParam(1, VT_INT) failure: 0x%08lX", + GetRDPSessionEventString(dispIdMember), hr); + return hr; + } + + level = vr.intVal; + vr.vt = VT_DISPATCH; + vr.pdispVal = NULL; + hr = DispGetParam(pDispParams, 0, VT_DISPATCH, &vr, &uArgErr); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "%s DispGetParam(0, VT_DISPATCH) failure: 0x%08lX", + GetRDPSessionEventString(dispIdMember), hr); + return hr; + } + + pDispatch = vr.pdispVal; + hr = pDispatch->lpVtbl->QueryInterface(pDispatch, &IID_IRDPSRAPIAttendee, + (void**)&pAttendee); + + if (FAILED(hr)) + { + WLog_INFO(TAG, "%s IDispatch::QueryInterface(IRDPSRAPIAttendee) failure: 0x%08lX", + GetRDPSessionEventString(dispIdMember), hr); + return hr; + } + + hr = pAttendee->lpVtbl->put_ControlLevel(pAttendee, level); + + if (FAILED(hr)) + { + WLog_INFO(TAG, "%s IRDPSRAPIAttendee::put_ControlLevel() failure: 0x%08lX", + GetRDPSessionEventString(dispIdMember), hr); + return hr; + } + + pAttendee->lpVtbl->Release(pAttendee); + } + break; + + case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_PAUSED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_GRAPHICS_STREAM_RESUMED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_JOIN: + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_LEAVE: + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_DATARECEIVED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_VIRTUAL_CHANNEL_SENDCOMPLETED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_OPEN: + break; + + case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_CLOSE: + break; + + case DISPID_RDPSRAPI_EVENT_ON_APPLICATION_UPDATE: + break; + + case DISPID_RDPSRAPI_EVENT_ON_WINDOW_OPEN: + break; + + case DISPID_RDPSRAPI_EVENT_ON_WINDOW_CLOSE: + break; + + case DISPID_RDPSRAPI_EVENT_ON_WINDOW_UPDATE: + break; + + case DISPID_RDPSRAPI_EVENT_ON_APPFILTER_UPDATE: + break; + + case DISPID_RDPSRAPI_EVENT_ON_SHARED_RECT_CHANGED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_FOCUSRELEASED: + break; + + case DISPID_RDPSRAPI_EVENT_ON_SHARED_DESKTOP_SETTINGS_CHANGED: + break; + + case DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED: + break; + } + + return S_OK; +} + +static _IRDPSessionEventsVtbl Shadow_IRDPSessionEventsVtbl = { + /* IUnknown */ + Shadow_IRDPSessionEvents_QueryInterface, Shadow_IRDPSessionEvents_AddRef, + Shadow_IRDPSessionEvents_Release, + + /* IDispatch */ + Shadow_IRDPSessionEvents_GetTypeInfoCount, Shadow_IRDPSessionEvents_GetTypeInfo, + Shadow_IRDPSessionEvents_GetIDsOfNames, Shadow_IRDPSessionEvents_Invoke +}; + +static _IRDPSessionEvents Shadow_IRDPSessionEvents = { &Shadow_IRDPSessionEventsVtbl }; + +static LRESULT CALLBACK ShadowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_CLOSE: + DestroyWindow(hwnd); + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hwnd, uMsg, wParam, lParam); + break; + } + + return 0; +} + +int win_shadow_wds_wnd_init(winShadowSubsystem* subsystem) +{ + HMODULE hModule; + HINSTANCE hInstance; + WNDCLASSEX wndClassEx = { 0 }; + hModule = GetModuleHandle(NULL); + + wndClassEx.cbSize = sizeof(WNDCLASSEX); + wndClassEx.style = 0; + wndClassEx.lpfnWndProc = ShadowWndProc; + wndClassEx.cbClsExtra = 0; + wndClassEx.cbWndExtra = 0; + wndClassEx.hInstance = hModule; + wndClassEx.hIcon = NULL; + wndClassEx.hCursor = NULL; + wndClassEx.hbrBackground = NULL; + wndClassEx.lpszMenuName = _T("ShadowWndMenu"); + wndClassEx.lpszClassName = _T("ShadowWndClass"); + wndClassEx.hIconSm = NULL; + + if (!RegisterClassEx(&wndClassEx)) + { + WLog_ERR(TAG, "RegisterClassEx failure"); + return -1; + } + + hInstance = wndClassEx.hInstance; + subsystem->hWnd = CreateWindowEx(0, wndClassEx.lpszClassName, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, + hInstance, NULL); + + if (!subsystem->hWnd) + { + WLog_INFO(TAG, "CreateWindowEx failure"); + return -1; + } + + return 1; +} + +int win_shadow_wds_init(winShadowSubsystem* subsystem) +{ + int status; + HRESULT hr; + DWORD dwCookie; + long left, top; + long right, bottom; + long width, height; + IUnknown* pUnknown; + rdpSettings* settings; + BSTR bstrAuthString; + BSTR bstrGroupName; + BSTR bstrPassword; + BSTR bstrPropertyName; + VARIANT varPropertyValue; + rdpAssistanceFile* file; + IConnectionPoint* pCP; + IConnectionPointContainer* pCPC; + win_shadow_wds_wnd_init(subsystem); + hr = OleInitialize(NULL); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "OleInitialize() failure"); + return -1; + } + + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "CoInitialize() failure"); + return -1; + } + + hr = CoCreateInstance(&CLSID_RDPSession, NULL, CLSCTX_ALL, &IID_IRDPSRAPISharingSession, + (void**)&(subsystem->pSharingSession)); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "CoCreateInstance(IRDPSRAPISharingSession) failure: 0x%08lX", hr); + return -1; + } + + pUnknown = (IUnknown*)subsystem->pSharingSession; + hr = pUnknown->lpVtbl->QueryInterface(pUnknown, &IID_IConnectionPointContainer, (void**)&pCPC); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "QueryInterface(IID_IConnectionPointContainer) failure: 0x%08lX", hr); + return -1; + } + + pCPC->lpVtbl->FindConnectionPoint(pCPC, &DIID__IRDPSessionEvents, &pCP); + + if (FAILED(hr)) + { + WLog_ERR( + TAG, + "IConnectionPointContainer::FindConnectionPoint(_IRDPSessionEvents) failure: 0x%08lX", + hr); + return -1; + } + + dwCookie = 0; + subsystem->pSessionEvents = &Shadow_IRDPSessionEvents; + subsystem->pSessionEvents->lpVtbl->AddRef(subsystem->pSessionEvents); + hr = pCP->lpVtbl->Advise(pCP, (IUnknown*)subsystem->pSessionEvents, &dwCookie); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IConnectionPoint::Advise(Shadow_IRDPSessionEvents) failure: 0x%08lX", hr); + return -1; + } + + hr = subsystem->pSharingSession->lpVtbl->put_ColorDepth(subsystem->pSharingSession, 32); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISharingSession::put_ColorDepth() failure: 0x%08lX", hr); + return -1; + } + + hr = subsystem->pSharingSession->lpVtbl->GetDesktopSharedRect(subsystem->pSharingSession, &left, + &top, &right, &bottom); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISharingSession::GetDesktopSharedRect() failure: 0x%08lX", hr); + return -1; + } + + width = right - left; + height = bottom - top; + WLog_INFO( + TAG, + "GetDesktopSharedRect(): left: %ld top: %ld right: %ld bottom: %ld width: %ld height: %ld", + left, top, right, bottom, width, height); + hr = subsystem->pSharingSession->lpVtbl->get_VirtualChannelManager( + subsystem->pSharingSession, &(subsystem->pVirtualChannelMgr)); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISharingSession::get_VirtualChannelManager() failure: 0x%08lX", hr); + return -1; + } + + hr = subsystem->pSharingSession->lpVtbl->get_ApplicationFilter( + subsystem->pSharingSession, &(subsystem->pApplicationFilter)); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISharingSession::get_ApplicationFilter() failure: 0x%08lX", hr); + return -1; + } + + hr = subsystem->pSharingSession->lpVtbl->get_Attendees(subsystem->pSharingSession, + &(subsystem->pAttendeeMgr)); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Attendees() failure: 0x%08lX", hr); + return -1; + } + + hr = subsystem->pSharingSession->lpVtbl->get_Properties(subsystem->pSharingSession, + &(subsystem->pSessionProperties)); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Properties() failure: 0x%08lX", hr); + return -1; + } + + bstrPropertyName = SysAllocString(L"PortId"); + varPropertyValue.vt = VT_I4; + varPropertyValue.intVal = 40000; + hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties, + bstrPropertyName, varPropertyValue); + SysFreeString(bstrPropertyName); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(PortId) failure: 0x%08lX", hr); + return -1; + } + + bstrPropertyName = SysAllocString(L"DrvConAttach"); + varPropertyValue.vt = VT_BOOL; + varPropertyValue.boolVal = VARIANT_TRUE; + hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties, + bstrPropertyName, varPropertyValue); + SysFreeString(bstrPropertyName); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(DrvConAttach) failure: 0x%08lX", + hr); + return -1; + } + + bstrPropertyName = SysAllocString(L"PortProtocol"); + varPropertyValue.vt = VT_I4; + // varPropertyValue.intVal = 0; // AF_UNSPEC + varPropertyValue.intVal = 2; // AF_INET + // varPropertyValue.intVal = 23; // AF_INET6 + hr = subsystem->pSessionProperties->lpVtbl->put_Property(subsystem->pSessionProperties, + bstrPropertyName, varPropertyValue); + SysFreeString(bstrPropertyName); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISessionProperties::put_Property(PortProtocol) failure: 0x%08lX", + hr); + return -1; + } + + hr = subsystem->pSharingSession->lpVtbl->Open(subsystem->pSharingSession); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISharingSession::Open() failure: 0x%08lX", hr); + return -1; + } + + hr = subsystem->pSharingSession->lpVtbl->get_Invitations(subsystem->pSharingSession, + &(subsystem->pInvitationMgr)); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPISharingSession::get_Invitations() failure"); + return -1; + } + + bstrAuthString = SysAllocString(L"Shadow"); + bstrGroupName = SysAllocString(L"ShadowGroup"); + bstrPassword = SysAllocString(L"Shadow123!"); + hr = subsystem->pInvitationMgr->lpVtbl->CreateInvitation( + subsystem->pInvitationMgr, bstrAuthString, bstrGroupName, bstrPassword, 5, + &(subsystem->pInvitation)); + SysFreeString(bstrAuthString); + SysFreeString(bstrGroupName); + SysFreeString(bstrPassword); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPIInvitationManager::CreateInvitation() failure: 0x%08lX", hr); + return -1; + } + + file = subsystem->pAssistanceFile = freerdp_assistance_file_new(); + + if (!file) + { + WLog_ERR(TAG, "freerdp_assistance_file_new() failed"); + return -1; + } + + { + int status2 = -1; + char* ConnectionString2; + BSTR bstrConnectionString; + hr = subsystem->pInvitation->lpVtbl->get_ConnectionString(subsystem->pInvitation, + &bstrConnectionString); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "IRDPSRAPIInvitation::get_ConnectionString() failure: 0x%08lX", hr); + return -1; + } + + ConnectionString2 = ConvertWCharToUtf8Alloc(bstrConnectionString, NULL); + SysFreeString(bstrConnectionString); + status2 = freerdp_assistance_set_connection_string2(file, ConnectionString2, "Shadow123!"); + free(ConnectionString2); + + if ((!ConnectionString2) || (status2 < 1)) + { + WLog_ERR(TAG, "failed to convert connection string"); + return -1; + } + } + + freerdp_assistance_print_file(file, WLog_Get(TAG), WLOG_INFO); + status = win_shadow_rdp_init(subsystem); + + if (status < 0) + { + WLog_ERR(TAG, "win_shadow_rdp_init() failure: %d", status); + return status; + } + + settings = subsystem->shw->settings; + status = freerdp_assistance_populate_settings_from_assistance_file(file, settings); + freerdp_settings_set_string(settings, FreeRDP_Domain, "RDP"); + freerdp_settings_set_string(settings, FreeRDP_Username, "Shadow"); + freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled, TRUE); + freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, width); + freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, height); + status = win_shadow_rdp_start(subsystem); + + if (status < 0) + { + WLog_ERR(TAG, "win_shadow_rdp_start() failure: %d", status); + return status; + } + + return 1; +} + +int win_shadow_wds_uninit(winShadowSubsystem* subsystem) +{ + if (subsystem->pSharingSession) + { + subsystem->pSharingSession->lpVtbl->Close(subsystem->pSharingSession); + subsystem->pSharingSession->lpVtbl->Release(subsystem->pSharingSession); + subsystem->pSharingSession = NULL; + } + + if (subsystem->pVirtualChannelMgr) + { + subsystem->pVirtualChannelMgr->lpVtbl->Release(subsystem->pVirtualChannelMgr); + subsystem->pVirtualChannelMgr = NULL; + } + + if (subsystem->pApplicationFilter) + { + subsystem->pApplicationFilter->lpVtbl->Release(subsystem->pApplicationFilter); + subsystem->pApplicationFilter = NULL; + } + + if (subsystem->pAttendeeMgr) + { + subsystem->pAttendeeMgr->lpVtbl->Release(subsystem->pAttendeeMgr); + subsystem->pAttendeeMgr = NULL; + } + + if (subsystem->pSessionProperties) + { + subsystem->pSessionProperties->lpVtbl->Release(subsystem->pSessionProperties); + subsystem->pSessionProperties = NULL; + } + + if (subsystem->pInvitationMgr) + { + subsystem->pInvitationMgr->lpVtbl->Release(subsystem->pInvitationMgr); + subsystem->pInvitationMgr = NULL; + } + + if (subsystem->pInvitation) + { + subsystem->pInvitation->lpVtbl->Release(subsystem->pInvitation); + subsystem->pInvitation = NULL; + } + + if (subsystem->pAssistanceFile) + { + freerdp_assistance_file_free(subsystem->pAssistanceFile); + subsystem->pAssistanceFile = NULL; + } + + if (subsystem->hWnd) + { + DestroyWindow(subsystem->hWnd); + subsystem->hWnd = NULL; + } + + if (subsystem->shw) + { + win_shadow_rdp_uninit(subsystem); + subsystem->shw = NULL; + } + + return 1; +} diff --git a/server/shadow/Win/win_wds.h b/server/shadow/Win/win_wds.h new file mode 100644 index 0000000..f8f3d80 --- /dev/null +++ b/server/shadow/Win/win_wds.h @@ -0,0 +1,48 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_WIN_WDS_H +#define FREERDP_SERVER_SHADOW_WIN_WDS_H + +#define WITH_WDS_API 1 + +#ifndef CINTERFACE +#define CINTERFACE +#endif + +#include <rdpencomapi.h> + +#ifndef DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED +#define DISPID_RDPAPI_EVENT_ON_BOUNDING_RECT_CHANGED 340 +#endif + +#include "win_shadow.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + int win_shadow_wds_init(winShadowSubsystem* subsystem); + int win_shadow_wds_uninit(winShadowSubsystem* subsystem); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_WIN_WDS_H */ diff --git a/server/shadow/X11/CMakeLists.txt b/server/shadow/X11/CMakeLists.txt new file mode 100644 index 0000000..0d88a0e --- /dev/null +++ b/server/shadow/X11/CMakeLists.txt @@ -0,0 +1,72 @@ +find_package(X11 REQUIRED) +if(X11_FOUND) + add_definitions(-DWITH_X11) + include_directories(${X11_INCLUDE_DIR}) + list(APPEND LIBS ${X11_LIBRARIES}) +endif() + +if(X11_XShm_FOUND) + add_definitions(-DWITH_XSHM) + include_directories(${X11_XShm_INCLUDE_PATH}) + list(APPEND LIBS ${X11_Xext_LIB}) +endif() + +if(X11_Xext_FOUND) + add_definitions(-DWITH_XEXT) + list(APPEND LIBS ${X11_Xext_LIB}) +endif() + +if(X11_Xinerama_FOUND) + add_definitions(-DWITH_XINERAMA) + include_directories(${X11_Xinerama_INCLUDE_PATH}) + list(APPEND LIBS ${X11_Xinerama_LIB}) +endif() + +if(X11_Xdamage_FOUND) + add_definitions(-DWITH_XDAMAGE) + include_directories(${X11_Xdamage_INCLUDE_PATH}) + list(APPEND LIBS ${X11_Xdamage_LIB}) +endif() + +if(X11_Xfixes_FOUND) + add_definitions(-DWITH_XFIXES) + include_directories(${X11_Xfixes_INCLUDE_PATH}) + list(APPEND LIBS ${X11_Xfixes_LIB}) +endif() + +if(X11_XTest_FOUND) + add_definitions(-DWITH_XTEST) + include_directories(${X11_XTest_INCLUDE_PATH}) + list(APPEND LIBS ${X11_XTest_LIB}) +endif() + +# XCursor and XRandr are currently not used so don't link them +#if(X11_Xcursor_FOUND) +# add_definitions(-DWITH_XCURSOR) +# include_directories(${X11_Xcursor_INCLUDE_PATH}) +# list(APPEND LIBS ${X11_Xcursor_LIB}) +#endif() + +#if(X11_Xrandr_FOUND) +# add_definitions(-DWITH_XRANDR) +# include_directories(${X11_Xrandr_INCLUDE_PATH}) +# list(APPEND LIBS ${X11_Xrandr_LIB}) +#endif() + +find_package(PAM) +if(PAM_FOUND) + add_definitions(-DWITH_PAM) + include_directories(${PAM_INCLUDE_DIR}) + list(APPEND LIBS ${PAM_LIBRARY}) +else() + message("building without PAM authentication support") +endif() + +add_definitions(-DWITH_SHADOW_X11) +add_library(freerdp-shadow-subsystem-impl STATIC + x11_shadow.h + x11_shadow.c +) +target_link_libraries(freerdp-shadow-subsystem-impl PRIVATE + ${LIBS} +) diff --git a/server/shadow/X11/x11_shadow.c b/server/shadow/X11/x11_shadow.c new file mode 100644 index 0000000..5c1fab1 --- /dev/null +++ b/server/shadow/X11/x11_shadow.c @@ -0,0 +1,1513 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 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 <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/ipc.h> +#include <sys/shm.h> +#include <sys/select.h> +#include <sys/signal.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/path.h> +#include <winpr/synch.h> +#include <winpr/image.h> +#include <winpr/sysinfo.h> + +#include <freerdp/log.h> +#include <freerdp/codec/color.h> +#include <freerdp/codec/region.h> + +#include "x11_shadow.h" + +#define TAG SERVER_TAG("shadow.x11") + +static UINT32 x11_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors); + +#ifdef WITH_PAM + +#include <security/pam_appl.h> + +typedef struct +{ + const char* user; + const char* domain; + const char* password; +} SHADOW_PAM_AUTH_DATA; + +typedef struct +{ + char* service_name; + pam_handle_t* handle; + struct pam_conv pamc; + SHADOW_PAM_AUTH_DATA appdata; +} SHADOW_PAM_AUTH_INFO; + +static int x11_shadow_pam_conv(int num_msg, const struct pam_message** msg, + struct pam_response** resp, void* appdata_ptr) +{ + int pam_status = PAM_CONV_ERR; + SHADOW_PAM_AUTH_DATA* appdata = NULL; + struct pam_response* response = NULL; + WINPR_ASSERT(num_msg >= 0); + appdata = (SHADOW_PAM_AUTH_DATA*)appdata_ptr; + WINPR_ASSERT(appdata); + + if (!(response = (struct pam_response*)calloc((size_t)num_msg, sizeof(struct pam_response)))) + return PAM_BUF_ERR; + + for (int index = 0; index < num_msg; index++) + { + switch (msg[index]->msg_style) + { + case PAM_PROMPT_ECHO_ON: + response[index].resp = _strdup(appdata->user); + + if (!response[index].resp) + goto out_fail; + + response[index].resp_retcode = PAM_SUCCESS; + break; + + case PAM_PROMPT_ECHO_OFF: + response[index].resp = _strdup(appdata->password); + + if (!response[index].resp) + goto out_fail; + + response[index].resp_retcode = PAM_SUCCESS; + break; + + default: + pam_status = PAM_CONV_ERR; + goto out_fail; + } + } + + *resp = response; + return PAM_SUCCESS; +out_fail: + + for (int index = 0; index < num_msg; ++index) + { + if (response[index].resp) + { + memset(response[index].resp, 0, strlen(response[index].resp)); + free(response[index].resp); + } + } + + memset(response, 0, sizeof(struct pam_response) * (size_t)num_msg); + free(response); + *resp = NULL; + return pam_status; +} + +static BOOL x11_shadow_pam_get_service_name(SHADOW_PAM_AUTH_INFO* info) +{ + const char* base = "/etc/pam.d"; + const char* hints[] = { "lightdm", "gdm", "xdm", "login", "sshd" }; + + for (size_t x = 0; x < ARRAYSIZE(hints); x++) + { + char path[MAX_PATH]; + const char* hint = hints[x]; + + _snprintf(path, sizeof(path), "%s/%s", base, hint); + if (winpr_PathFileExists(path)) + { + + info->service_name = _strdup(hint); + return info->service_name != NULL; + } + } + WLog_WARN(TAG, "Could not determine PAM service name"); + return FALSE; +} + +static int x11_shadow_pam_authenticate(rdpShadowSubsystem* subsystem, rdpShadowClient* client, + const char* user, const char* domain, const char* password) +{ + int pam_status = 0; + SHADOW_PAM_AUTH_INFO info = { 0 }; + WINPR_UNUSED(subsystem); + WINPR_UNUSED(client); + + if (!x11_shadow_pam_get_service_name(&info)) + return -1; + + info.appdata.user = user; + info.appdata.domain = domain; + info.appdata.password = password; + info.pamc.conv = &x11_shadow_pam_conv; + info.pamc.appdata_ptr = &info.appdata; + pam_status = pam_start(info.service_name, 0, &info.pamc, &info.handle); + + if (pam_status != PAM_SUCCESS) + { + WLog_ERR(TAG, "pam_start failure: %s", pam_strerror(info.handle, pam_status)); + return -1; + } + + pam_status = pam_authenticate(info.handle, 0); + + if (pam_status != PAM_SUCCESS) + { + WLog_ERR(TAG, "pam_authenticate failure: %s", pam_strerror(info.handle, pam_status)); + return -1; + } + + pam_status = pam_acct_mgmt(info.handle, 0); + + if (pam_status != PAM_SUCCESS) + { + WLog_ERR(TAG, "pam_acct_mgmt failure: %s", pam_strerror(info.handle, pam_status)); + return -1; + } + + return 1; +} + +#endif + +static BOOL x11_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT32 flags) +{ + /* TODO: Implement */ + WLog_WARN(TAG, "not implemented"); + return TRUE; +} + +static BOOL x11_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client, + UINT16 flags, UINT8 code) +{ +#ifdef WITH_XTEST + x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem; + DWORD vkcode = 0; + DWORD keycode = 0; + DWORD scancode = 0; + BOOL extended = FALSE; + + if (!client || !subsystem) + return FALSE; + + if (flags & KBD_FLAGS_EXTENDED) + extended = TRUE; + + scancode = code; + if (extended) + scancode |= KBDEXT; + + vkcode = GetVirtualKeyCodeFromVirtualScanCode(scancode, WINPR_KBD_TYPE_IBM_ENHANCED); + + if (extended) + vkcode |= KBDEXT; + + keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_XKB); + + if (keycode != 0) + { + XLockDisplay(x11->display); + XTestGrabControl(x11->display, True); + + if (flags & KBD_FLAGS_RELEASE) + XTestFakeKeyEvent(x11->display, keycode, False, CurrentTime); + else + XTestFakeKeyEvent(x11->display, keycode, True, CurrentTime); + + XTestGrabControl(x11->display, False); + XFlush(x11->display); + XUnlockDisplay(x11->display); + } + +#endif + return TRUE; +} + +static BOOL x11_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT16 flags, + UINT16 code) +{ + /* TODO: Implement */ + WLog_WARN(TAG, "not implemented"); + return TRUE; +} + +static BOOL x11_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client, + UINT16 flags, UINT16 x, UINT16 y) +{ +#ifdef WITH_XTEST + x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem; + unsigned int button = 0; + BOOL down = FALSE; + rdpShadowServer* server = NULL; + rdpShadowSurface* surface = NULL; + + if (!subsystem || !client) + return FALSE; + + server = subsystem->server; + + if (!server) + return FALSE; + + surface = server->surface; + + if (!surface) + return FALSE; + + x11->lastMouseClient = client; + x += surface->x; + y += surface->y; + XLockDisplay(x11->display); + XTestGrabControl(x11->display, True); + + if (flags & PTR_FLAGS_WHEEL) + { + BOOL negative = FALSE; + + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + negative = TRUE; + + button = (negative) ? 5 : 4; + XTestFakeButtonEvent(x11->display, button, True, (unsigned long)CurrentTime); + XTestFakeButtonEvent(x11->display, button, False, (unsigned long)CurrentTime); + } + else if (flags & PTR_FLAGS_HWHEEL) + { + BOOL negative = FALSE; + + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + negative = TRUE; + + button = (negative) ? 7 : 6; + XTestFakeButtonEvent(x11->display, button, True, (unsigned long)CurrentTime); + XTestFakeButtonEvent(x11->display, button, False, (unsigned long)CurrentTime); + } + else + { + if (flags & PTR_FLAGS_MOVE) + XTestFakeMotionEvent(x11->display, 0, x, y, CurrentTime); + + if (flags & PTR_FLAGS_BUTTON1) + button = 1; + else if (flags & PTR_FLAGS_BUTTON2) + button = 3; + else if (flags & PTR_FLAGS_BUTTON3) + button = 2; + + if (flags & PTR_FLAGS_DOWN) + down = TRUE; + + if (button) + XTestFakeButtonEvent(x11->display, button, down, CurrentTime); + } + + XTestGrabControl(x11->display, False); + XFlush(x11->display); + XUnlockDisplay(x11->display); +#endif + return TRUE; +} + +static BOOL x11_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem, + rdpShadowClient* client, UINT16 flags, UINT16 x, + UINT16 y) +{ +#ifdef WITH_XTEST + x11ShadowSubsystem* x11 = (x11ShadowSubsystem*)subsystem; + UINT button = 0; + BOOL down = FALSE; + rdpShadowServer* server = NULL; + rdpShadowSurface* surface = NULL; + + if (!subsystem || !client) + return FALSE; + + server = subsystem->server; + + if (!server) + return FALSE; + + surface = server->surface; + + if (!surface) + return FALSE; + + x11->lastMouseClient = client; + x += surface->x; + y += surface->y; + XLockDisplay(x11->display); + XTestGrabControl(x11->display, True); + XTestFakeMotionEvent(x11->display, 0, x, y, CurrentTime); + + if (flags & PTR_XFLAGS_BUTTON1) + button = 8; + else if (flags & PTR_XFLAGS_BUTTON2) + button = 9; + + if (flags & PTR_XFLAGS_DOWN) + down = TRUE; + + if (button) + XTestFakeButtonEvent(x11->display, button, down, CurrentTime); + + XTestGrabControl(x11->display, False); + XFlush(x11->display); + XUnlockDisplay(x11->display); +#endif + return TRUE; +} + +static void x11_shadow_message_free(UINT32 id, SHADOW_MSG_OUT* msg) +{ + switch (id) + { + case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID: + free(msg); + break; + + case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID: + free(((SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)msg)->xorMaskData); + free(((SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)msg)->andMaskData); + free(msg); + break; + + default: + WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", id); + free(msg); + break; + } +} + +static int x11_shadow_pointer_position_update(x11ShadowSubsystem* subsystem) +{ + UINT32 msgId = SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID; + rdpShadowServer* server = NULL; + SHADOW_MSG_OUT_POINTER_POSITION_UPDATE templateMsg; + int count = 0; + + if (!subsystem || !subsystem->common.server || !subsystem->common.server->clients) + return -1; + + templateMsg.xPos = subsystem->common.pointerX; + templateMsg.yPos = subsystem->common.pointerY; + templateMsg.common.Free = x11_shadow_message_free; + server = subsystem->common.server; + ArrayList_Lock(server->clients); + + for (size_t index = 0; index < ArrayList_Count(server->clients); index++) + { + SHADOW_MSG_OUT_POINTER_POSITION_UPDATE* msg = NULL; + rdpShadowClient* client = (rdpShadowClient*)ArrayList_GetItem(server->clients, index); + + /* Skip the client which send us the latest mouse event */ + if (client == subsystem->lastMouseClient) + continue; + + msg = malloc(sizeof(templateMsg)); + + if (!msg) + { + count = -1; + break; + } + + memcpy(msg, &templateMsg, sizeof(templateMsg)); + + if (shadow_client_post_msg(client, NULL, msgId, (SHADOW_MSG_OUT*)msg, NULL)) + count++; + } + + ArrayList_Unlock(server->clients); + return count; +} + +static int x11_shadow_pointer_alpha_update(x11ShadowSubsystem* subsystem) +{ + SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* msg = NULL; + UINT32 msgId = SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID; + msg = (SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)calloc(1, + sizeof(SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE)); + + if (!msg) + return -1; + + msg->xHot = subsystem->cursorHotX; + msg->yHot = subsystem->cursorHotY; + msg->width = subsystem->cursorWidth; + msg->height = subsystem->cursorHeight; + + if (shadow_subsystem_pointer_convert_alpha_pointer_data(subsystem->cursorPixels, TRUE, + msg->width, msg->height, msg) < 0) + { + free(msg); + return -1; + } + + msg->common.Free = x11_shadow_message_free; + return shadow_client_boardcast_msg(subsystem->common.server, NULL, msgId, (SHADOW_MSG_OUT*)msg, + NULL) + ? 1 + : -1; +} + +static int x11_shadow_query_cursor(x11ShadowSubsystem* subsystem, BOOL getImage) +{ + int x = 0; + int y = 0; + int n = 0; + rdpShadowServer* server = NULL; + rdpShadowSurface* surface = NULL; + server = subsystem->common.server; + surface = server->surface; + + if (getImage) + { +#ifdef WITH_XFIXES + UINT32* pDstPixel = NULL; + XFixesCursorImage* ci = NULL; + XLockDisplay(subsystem->display); + ci = XFixesGetCursorImage(subsystem->display); + XUnlockDisplay(subsystem->display); + + if (!ci) + return -1; + + x = ci->x; + y = ci->y; + + if (ci->width > subsystem->cursorMaxWidth) + return -1; + + if (ci->height > subsystem->cursorMaxHeight) + return -1; + + subsystem->cursorHotX = ci->xhot; + subsystem->cursorHotY = ci->yhot; + subsystem->cursorWidth = ci->width; + subsystem->cursorHeight = ci->height; + subsystem->cursorId = ci->cursor_serial; + n = ci->width * ci->height; + pDstPixel = (UINT32*)subsystem->cursorPixels; + + for (int k = 0; k < n; k++) + { + /* XFixesCursorImage.pixels is in *unsigned long*, which may be 8 bytes */ + *pDstPixel++ = (UINT32)ci->pixels[k]; + } + + XFree(ci); + x11_shadow_pointer_alpha_update(subsystem); +#endif + } + else + { + UINT32 mask = 0; + int win_x = 0; + int win_y = 0; + int root_x = 0; + int root_y = 0; + Window root = 0; + Window child = 0; + XLockDisplay(subsystem->display); + + if (!XQueryPointer(subsystem->display, subsystem->root_window, &root, &child, &root_x, + &root_y, &win_x, &win_y, &mask)) + { + XUnlockDisplay(subsystem->display); + return -1; + } + + XUnlockDisplay(subsystem->display); + x = root_x; + y = root_y; + } + + /* Convert to offset based on current surface */ + if (surface) + { + x -= surface->x; + y -= surface->y; + } + + if ((x != (INT64)subsystem->common.pointerX) || (y != (INT64)subsystem->common.pointerY)) + { + subsystem->common.pointerX = x; + subsystem->common.pointerY = y; + x11_shadow_pointer_position_update(subsystem); + } + + return 1; +} + +static int x11_shadow_handle_xevent(x11ShadowSubsystem* subsystem, XEvent* xevent) +{ + if (xevent->type == MotionNotify) + { + } + +#ifdef WITH_XFIXES + else if (xevent->type == subsystem->xfixes_cursor_notify_event) + { + x11_shadow_query_cursor(subsystem, TRUE); + } + +#endif + else + { + } + + return 1; +} + +static void x11_shadow_validate_region(x11ShadowSubsystem* subsystem, int x, int y, int width, + int height) +{ + XRectangle region; + + if (!subsystem->use_xfixes || !subsystem->use_xdamage) + return; + + region.x = x; + region.y = y; + region.width = width; + region.height = height; +#if defined(WITH_XFIXES) && defined(WITH_XDAMAGE) + XLockDisplay(subsystem->display); + XFixesSetRegion(subsystem->display, subsystem->xdamage_region, ®ion, 1); + XDamageSubtract(subsystem->display, subsystem->xdamage, subsystem->xdamage_region, None); + XUnlockDisplay(subsystem->display); +#endif +} + +static int x11_shadow_blend_cursor(x11ShadowSubsystem* subsystem) +{ + UINT32 nXSrc = 0; + UINT32 nYSrc = 0; + INT64 nXDst = 0; + INT64 nYDst = 0; + UINT32 nWidth = 0; + UINT32 nHeight = 0; + UINT32 nSrcStep = 0; + UINT32 nDstStep = 0; + BYTE* pSrcData = NULL; + BYTE* pDstData = NULL; + BYTE A = 0; + BYTE R = 0; + BYTE G = 0; + BYTE B = 0; + rdpShadowSurface* surface = NULL; + + if (!subsystem) + return -1; + + surface = subsystem->common.server->surface; + nXSrc = 0; + nYSrc = 0; + nWidth = subsystem->cursorWidth; + nHeight = subsystem->cursorHeight; + nXDst = subsystem->common.pointerX - subsystem->cursorHotX; + nYDst = subsystem->common.pointerY - subsystem->cursorHotY; + + if (nXDst >= surface->width) + return 1; + + if (nXDst < 0) + { + nXDst *= -1; + + if (nXDst >= nWidth) + return 1; + + nXSrc = (UINT32)nXDst; + nWidth -= nXDst; + nXDst = 0; + } + + if (nYDst >= surface->height) + return 1; + + if (nYDst < 0) + { + nYDst *= -1; + + if (nYDst >= nHeight) + return 1; + + nYSrc = (UINT32)nYDst; + nHeight -= nYDst; + nYDst = 0; + } + + if ((nXDst + nWidth) > surface->width) + nWidth = surface->width - nXDst; + + if ((nYDst + nHeight) > surface->height) + nHeight = surface->height - nYDst; + + pSrcData = subsystem->cursorPixels; + nSrcStep = subsystem->cursorWidth * 4; + pDstData = surface->data; + nDstStep = surface->scanline; + + for (int y = 0; y < nHeight; y++) + { + const BYTE* pSrcPixel = &pSrcData[((nYSrc + y) * nSrcStep) + (nXSrc * 4)]; + BYTE* pDstPixel = &pDstData[((nYDst + y) * nDstStep) + (nXDst * 4)]; + + for (int x = 0; x < nWidth; x++) + { + B = *pSrcPixel++; + G = *pSrcPixel++; + R = *pSrcPixel++; + A = *pSrcPixel++; + + if (A == 0xFF) + { + pDstPixel[0] = B; + pDstPixel[1] = G; + pDstPixel[2] = R; + } + else + { + pDstPixel[0] = B + (pDstPixel[0] * (0xFF - A) + (0xFF / 2)) / 0xFF; + pDstPixel[1] = G + (pDstPixel[1] * (0xFF - A) + (0xFF / 2)) / 0xFF; + pDstPixel[2] = R + (pDstPixel[2] * (0xFF - A) + (0xFF / 2)) / 0xFF; + } + + pDstPixel[3] = 0xFF; + pDstPixel += 4; + } + } + + return 1; +} + +static BOOL x11_shadow_check_resize(x11ShadowSubsystem* subsystem) +{ + XWindowAttributes attr; + XLockDisplay(subsystem->display); + XGetWindowAttributes(subsystem->display, subsystem->root_window, &attr); + XUnlockDisplay(subsystem->display); + + if (attr.width != (INT64)subsystem->width || attr.height != (INT64)subsystem->height) + { + MONITOR_DEF* virtualScreen = &(subsystem->common.virtualScreen); + + /* Screen size changed. Refresh monitor definitions and trigger screen resize */ + subsystem->common.numMonitors = x11_shadow_enum_monitors(subsystem->common.monitors, 16); + shadow_screen_resize(subsystem->common.server->screen); + subsystem->width = attr.width; + subsystem->height = attr.height; + + virtualScreen->left = 0; + virtualScreen->top = 0; + virtualScreen->right = subsystem->width - 1; + virtualScreen->bottom = subsystem->height - 1; + virtualScreen->flags = 1; + return TRUE; + } + + return FALSE; +} + +static int x11_shadow_error_handler_for_capture(Display* display, XErrorEvent* event) +{ + char msg[256]; + XGetErrorText(display, event->error_code, (char*)&msg, sizeof(msg)); + WLog_ERR(TAG, "X11 error: %s Error code: %x, request code: %x, minor code: %x", msg, + event->error_code, event->request_code, event->minor_code); + + /* Ignore BAD MATCH error during image capture. Abort in other case */ + if (event->error_code != BadMatch) + { + abort(); + } + + return 0; +} + +static int x11_shadow_screen_grab(x11ShadowSubsystem* subsystem) +{ + int rc = 0; + size_t count = 0; + int status = -1; + int x = 0; + int y = 0; + int width = 0; + int height = 0; + XImage* image = NULL; + rdpShadowServer* server = NULL; + rdpShadowSurface* surface = NULL; + RECTANGLE_16 invalidRect; + RECTANGLE_16 surfaceRect; + const RECTANGLE_16* extents = NULL; + server = subsystem->common.server; + surface = server->surface; + count = ArrayList_Count(server->clients); + + if (count < 1) + return 1; + + EnterCriticalSection(&surface->lock); + surfaceRect.left = 0; + surfaceRect.top = 0; + surfaceRect.right = surface->width; + surfaceRect.bottom = surface->height; + LeaveCriticalSection(&surface->lock); + + XLockDisplay(subsystem->display); + /* + * Ignore BadMatch error during image capture. The screen size may be + * changed outside. We will resize to correct resolution at next frame + */ + XSetErrorHandler(x11_shadow_error_handler_for_capture); +#if defined(WITH_XDAMAGE) + if (subsystem->use_xshm) + { + image = subsystem->fb_image; + XCopyArea(subsystem->display, subsystem->root_window, subsystem->fb_pixmap, + subsystem->xshm_gc, 0, 0, subsystem->width, subsystem->height, 0, 0); + + EnterCriticalSection(&surface->lock); + status = shadow_capture_compare(surface->data, surface->scanline, surface->width, + surface->height, (BYTE*)&(image->data[surface->width * 4]), + image->bytes_per_line, &invalidRect); + LeaveCriticalSection(&surface->lock); + } + else +#endif + { + EnterCriticalSection(&surface->lock); + image = XGetImage(subsystem->display, subsystem->root_window, surface->x, surface->y, + surface->width, surface->height, AllPlanes, ZPixmap); + + if (image) + { + status = shadow_capture_compare(surface->data, surface->scanline, surface->width, + surface->height, (BYTE*)image->data, + image->bytes_per_line, &invalidRect); + } + LeaveCriticalSection(&surface->lock); + if (!image) + { + /* + * BadMatch error happened. The size may have been changed again. + * Give up this frame and we will resize again in next frame + */ + goto fail_capture; + } + } + + /* Restore the default error handler */ + XSetErrorHandler(NULL); + XSync(subsystem->display, False); + XUnlockDisplay(subsystem->display); + + if (status) + { + BOOL empty = 0; + EnterCriticalSection(&surface->lock); + region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect); + region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect); + empty = region16_is_empty(&(surface->invalidRegion)); + LeaveCriticalSection(&surface->lock); + + if (!empty) + { + BOOL success = 0; + EnterCriticalSection(&surface->lock); + extents = region16_extents(&(surface->invalidRegion)); + x = extents->left; + y = extents->top; + width = extents->right - extents->left; + height = extents->bottom - extents->top; + WINPR_ASSERT(image); + WINPR_ASSERT(image->bytes_per_line >= 0); + WINPR_ASSERT(width >= 0); + WINPR_ASSERT(height >= 0); + success = freerdp_image_copy(surface->data, surface->format, surface->scanline, x, y, + (UINT32)width, (UINT32)height, (BYTE*)image->data, + PIXEL_FORMAT_BGRX32, (UINT32)image->bytes_per_line, x, y, + NULL, FREERDP_FLIP_NONE); + LeaveCriticalSection(&surface->lock); + if (!success) + goto fail_capture; + + // x11_shadow_blend_cursor(subsystem); + count = ArrayList_Count(server->clients); + shadow_subsystem_frame_update(&subsystem->common); + + if (count == 1) + { + rdpShadowClient* client = NULL; + client = (rdpShadowClient*)ArrayList_GetItem(server->clients, 0); + + if (client) + subsystem->common.captureFrameRate = + shadow_encoder_preferred_fps(client->encoder); + } + + EnterCriticalSection(&surface->lock); + region16_clear(&(surface->invalidRegion)); + LeaveCriticalSection(&surface->lock); + } + } + + rc = 1; +fail_capture: + if (!subsystem->use_xshm && image) + XDestroyImage(image); + + if (rc != 1) + { + XSetErrorHandler(NULL); + XSync(subsystem->display, False); + XUnlockDisplay(subsystem->display); + } + + return rc; +} + +static int x11_shadow_subsystem_process_message(x11ShadowSubsystem* subsystem, wMessage* message) +{ + switch (message->id) + { + case SHADOW_MSG_IN_REFRESH_REQUEST_ID: + shadow_subsystem_frame_update((rdpShadowSubsystem*)subsystem); + break; + + default: + WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id); + break; + } + + if (message->Free) + message->Free(message); + + return 1; +} + +static DWORD WINAPI x11_shadow_subsystem_thread(LPVOID arg) +{ + x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)arg; + XEvent xevent; + DWORD status = 0; + DWORD nCount = 0; + UINT64 cTime = 0; + DWORD dwTimeout = 0; + DWORD dwInterval = 0; + UINT64 frameTime = 0; + HANDLE events[32]; + wMessage message; + wMessagePipe* MsgPipe = NULL; + MsgPipe = subsystem->common.MsgPipe; + nCount = 0; + events[nCount++] = subsystem->common.event; + events[nCount++] = MessageQueue_Event(MsgPipe->In); + subsystem->common.captureFrameRate = 16; + dwInterval = 1000 / subsystem->common.captureFrameRate; + frameTime = GetTickCount64() + dwInterval; + + while (1) + { + cTime = GetTickCount64(); + dwTimeout = (cTime > frameTime) ? 0 : frameTime - cTime; + status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout); + + if (WaitForSingleObject(MessageQueue_Event(MsgPipe->In), 0) == WAIT_OBJECT_0) + { + if (MessageQueue_Peek(MsgPipe->In, &message, TRUE)) + { + if (message.id == WMQ_QUIT) + break; + + x11_shadow_subsystem_process_message(subsystem, &message); + } + } + + if (WaitForSingleObject(subsystem->common.event, 0) == WAIT_OBJECT_0) + { + XLockDisplay(subsystem->display); + + if (XEventsQueued(subsystem->display, QueuedAlready)) + { + XNextEvent(subsystem->display, &xevent); + x11_shadow_handle_xevent(subsystem, &xevent); + } + + XUnlockDisplay(subsystem->display); + } + + if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime)) + { + x11_shadow_check_resize(subsystem); + x11_shadow_screen_grab(subsystem); + x11_shadow_query_cursor(subsystem, FALSE); + dwInterval = 1000 / subsystem->common.captureFrameRate; + frameTime += dwInterval; + } + } + + ExitThread(0); + return 0; +} + +static int x11_shadow_subsystem_base_init(x11ShadowSubsystem* subsystem) +{ + if (subsystem->display) + return 1; /* initialize once */ + + if (!getenv("DISPLAY")) + setenv("DISPLAY", ":0", 1); + + if (!XInitThreads()) + return -1; + + subsystem->display = XOpenDisplay(NULL); + + if (!subsystem->display) + { + WLog_ERR(TAG, "failed to open display: %s", XDisplayName(NULL)); + return -1; + } + + subsystem->xfds = ConnectionNumber(subsystem->display); + subsystem->number = DefaultScreen(subsystem->display); + subsystem->screen = ScreenOfDisplay(subsystem->display, subsystem->number); + subsystem->depth = DefaultDepthOfScreen(subsystem->screen); + subsystem->width = WidthOfScreen(subsystem->screen); + subsystem->height = HeightOfScreen(subsystem->screen); + subsystem->root_window = RootWindow(subsystem->display, subsystem->number); + return 1; +} + +static int x11_shadow_xfixes_init(x11ShadowSubsystem* subsystem) +{ +#ifdef WITH_XFIXES + int xfixes_event = 0; + int xfixes_error = 0; + int major = 0; + int minor = 0; + + if (!XFixesQueryExtension(subsystem->display, &xfixes_event, &xfixes_error)) + return -1; + + if (!XFixesQueryVersion(subsystem->display, &major, &minor)) + return -1; + + subsystem->xfixes_cursor_notify_event = xfixes_event + XFixesCursorNotify; + XFixesSelectCursorInput(subsystem->display, subsystem->root_window, + XFixesDisplayCursorNotifyMask); + return 1; +#else + return -1; +#endif +} + +static int x11_shadow_xinerama_init(x11ShadowSubsystem* subsystem) +{ +#ifdef WITH_XINERAMA + int xinerama_event = 0; + int xinerama_error = 0; + x11_shadow_subsystem_base_init(subsystem); + + if (!XineramaQueryExtension(subsystem->display, &xinerama_event, &xinerama_error)) + return -1; + +#if defined(WITH_XDAMAGE) + int major = 0; + int minor = 0; + if (!XDamageQueryVersion(subsystem->display, &major, &minor)) + return -1; +#endif + + if (!XineramaIsActive(subsystem->display)) + return -1; + + return 1; +#else + return -1; +#endif +} + +static int x11_shadow_xdamage_init(x11ShadowSubsystem* subsystem) +{ +#ifdef WITH_XDAMAGE + int major = 0; + int minor = 0; + int damage_event = 0; + int damage_error = 0; + + if (!subsystem->use_xfixes) + return -1; + + if (!XDamageQueryExtension(subsystem->display, &damage_event, &damage_error)) + return -1; + + if (!XDamageQueryVersion(subsystem->display, &major, &minor)) + return -1; + + if (major < 1) + return -1; + + subsystem->xdamage_notify_event = damage_event + XDamageNotify; + subsystem->xdamage = + XDamageCreate(subsystem->display, subsystem->root_window, XDamageReportDeltaRectangles); + + if (!subsystem->xdamage) + return -1; + +#ifdef WITH_XFIXES + subsystem->xdamage_region = XFixesCreateRegion(subsystem->display, NULL, 0); + + if (!subsystem->xdamage_region) + return -1; + +#endif + return 1; +#else + return -1; +#endif +} + +static int x11_shadow_xshm_init(x11ShadowSubsystem* subsystem) +{ + Bool pixmaps = 0; + int major = 0; + int minor = 0; + XGCValues values; + + if (!XShmQueryExtension(subsystem->display)) + return -1; + + if (!XShmQueryVersion(subsystem->display, &major, &minor, &pixmaps)) + return -1; + + if (!pixmaps) + return -1; + + subsystem->fb_shm_info.shmid = -1; + subsystem->fb_shm_info.shmaddr = (char*)-1; + subsystem->fb_shm_info.readOnly = False; + subsystem->fb_image = + XShmCreateImage(subsystem->display, subsystem->visual, subsystem->depth, ZPixmap, NULL, + &(subsystem->fb_shm_info), subsystem->width, subsystem->height); + + if (!subsystem->fb_image) + { + WLog_ERR(TAG, "XShmCreateImage failed"); + return -1; + } + + subsystem->fb_shm_info.shmid = shmget( + IPC_PRIVATE, 1ull * subsystem->fb_image->bytes_per_line * subsystem->fb_image->height, + IPC_CREAT | 0600); + + if (subsystem->fb_shm_info.shmid == -1) + { + WLog_ERR(TAG, "shmget failed"); + return -1; + } + + subsystem->fb_shm_info.shmaddr = shmat(subsystem->fb_shm_info.shmid, 0, 0); + subsystem->fb_image->data = subsystem->fb_shm_info.shmaddr; + + if (subsystem->fb_shm_info.shmaddr == ((char*)-1)) + { + WLog_ERR(TAG, "shmat failed"); + return -1; + } + + if (!XShmAttach(subsystem->display, &(subsystem->fb_shm_info))) + return -1; + + XSync(subsystem->display, False); + shmctl(subsystem->fb_shm_info.shmid, IPC_RMID, 0); + subsystem->fb_pixmap = + XShmCreatePixmap(subsystem->display, subsystem->root_window, subsystem->fb_image->data, + &(subsystem->fb_shm_info), subsystem->fb_image->width, + subsystem->fb_image->height, subsystem->fb_image->depth); + XSync(subsystem->display, False); + + if (!subsystem->fb_pixmap) + return -1; + + values.subwindow_mode = IncludeInferiors; + values.graphics_exposures = False; +#if defined(WITH_XDAMAGE) + subsystem->xshm_gc = XCreateGC(subsystem->display, subsystem->root_window, + GCSubwindowMode | GCGraphicsExposures, &values); + XSetFunction(subsystem->display, subsystem->xshm_gc, GXcopy); +#endif + XSync(subsystem->display, False); + return 1; +} + +UINT32 x11_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors) +{ + Display* display = NULL; + int displayWidth = 0; + int displayHeight = 0; + int numMonitors = 0; + + if (!getenv("DISPLAY")) + setenv("DISPLAY", ":0", 1); + + display = XOpenDisplay(NULL); + + if (!display) + { + WLog_ERR(TAG, "failed to open display: %s", XDisplayName(NULL)); + return -1; + } + + displayWidth = WidthOfScreen(DefaultScreenOfDisplay(display)); + displayHeight = HeightOfScreen(DefaultScreenOfDisplay(display)); +#ifdef WITH_XINERAMA + { +#if defined(WITH_XDAMAGE) + int major = 0; + int minor = 0; +#endif + int xinerama_event = 0; + int xinerama_error = 0; + XineramaScreenInfo* screens = NULL; + + const Bool xinerama = XineramaQueryExtension(display, &xinerama_event, &xinerama_error); + const Bool damage = +#if defined(WITH_XDAMAGE) + XDamageQueryVersion(display, &major, &minor); +#else + False; +#endif + + if (xinerama && damage && XineramaIsActive(display)) + { + screens = XineramaQueryScreens(display, &numMonitors); + + if (numMonitors > (INT64)maxMonitors) + numMonitors = (int)maxMonitors; + + if (screens && (numMonitors > 0)) + { + for (int index = 0; index < numMonitors; index++) + { + MONITOR_DEF* monitor = &monitors[index]; + const XineramaScreenInfo* screen = &screens[index]; + + monitor->left = screen->x_org; + monitor->top = screen->y_org; + monitor->right = monitor->left + screen->width - 1; + monitor->bottom = monitor->top + screen->height - 1; + monitor->flags = (index == 0) ? 1 : 0; + } + } + + XFree(screens); + } + } +#endif + XCloseDisplay(display); + + if (numMonitors < 1) + { + MONITOR_DEF* monitor = &monitors[0]; + numMonitors = 1; + + monitor->left = 0; + monitor->top = 0; + monitor->right = displayWidth - 1; + monitor->bottom = displayHeight - 1; + monitor->flags = 1; + } + + errno = 0; + return numMonitors; +} + +static int x11_shadow_subsystem_init(rdpShadowSubsystem* sub) +{ + int pf_count = 0; + int vi_count = 0; + int nextensions = 0; + char** extensions = NULL; + XVisualInfo* vi = NULL; + XVisualInfo* vis = NULL; + XVisualInfo template = { 0 }; + XPixmapFormatValues* pf = NULL; + XPixmapFormatValues* pfs = NULL; + + x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub; + + if (!subsystem) + return -1; + + subsystem->common.numMonitors = x11_shadow_enum_monitors(subsystem->common.monitors, 16); + x11_shadow_subsystem_base_init(subsystem); + + if ((subsystem->depth != 24) && (subsystem->depth != 32)) + { + WLog_ERR(TAG, "unsupported X11 server color depth: %" PRIu32, subsystem->depth); + return -1; + } + + extensions = XListExtensions(subsystem->display, &nextensions); + + if (!extensions || (nextensions < 0)) + return -1; + + for (int i = 0; i < nextensions; i++) + { + if (strcmp(extensions[i], "Composite") == 0) + subsystem->composite = TRUE; + } + + XFreeExtensionList(extensions); + + if (subsystem->composite) + subsystem->use_xdamage = FALSE; + + pfs = XListPixmapFormats(subsystem->display, &pf_count); + + if (!pfs) + { + WLog_ERR(TAG, "XListPixmapFormats failed"); + return -1; + } + + for (int i = 0; i < pf_count; i++) + { + pf = pfs + i; + + if (pf->depth == (INT64)subsystem->depth) + { + subsystem->bpp = pf->bits_per_pixel; + subsystem->scanline_pad = pf->scanline_pad; + break; + } + } + + XFree(pfs); + template.class = TrueColor; + template.screen = subsystem->number; + vis = XGetVisualInfo(subsystem->display, VisualClassMask | VisualScreenMask, &template, + &vi_count); + + if (!vis) + { + WLog_ERR(TAG, "XGetVisualInfo failed"); + return -1; + } + + for (int i = 0; i < vi_count; i++) + { + vi = vis + i; + + if (vi->depth == (INT64)subsystem->depth) + { + subsystem->visual = vi->visual; + break; + } + } + + XFree(vis); + XSelectInput(subsystem->display, subsystem->root_window, SubstructureNotifyMask); + subsystem->cursorMaxWidth = 256; + subsystem->cursorMaxHeight = 256; + subsystem->cursorPixels = + winpr_aligned_malloc(subsystem->cursorMaxWidth * subsystem->cursorMaxHeight * 4ull, 16); + + if (!subsystem->cursorPixels) + return -1; + + x11_shadow_query_cursor(subsystem, TRUE); + + if (subsystem->use_xfixes) + { + if (x11_shadow_xfixes_init(subsystem) < 0) + subsystem->use_xfixes = FALSE; + } + + if (subsystem->use_xinerama) + { + if (x11_shadow_xinerama_init(subsystem) < 0) + subsystem->use_xinerama = FALSE; + } + + if (subsystem->use_xshm) + { + if (x11_shadow_xshm_init(subsystem) < 0) + subsystem->use_xshm = FALSE; + } + + if (subsystem->use_xdamage) + { + if (x11_shadow_xdamage_init(subsystem) < 0) + subsystem->use_xdamage = FALSE; + } + + if (!(subsystem->common.event = + CreateFileDescriptorEvent(NULL, FALSE, FALSE, subsystem->xfds, WINPR_FD_READ))) + return -1; + + { + MONITOR_DEF* virtualScreen = &(subsystem->common.virtualScreen); + virtualScreen->left = 0; + virtualScreen->top = 0; + WINPR_ASSERT(subsystem->width <= INT32_MAX); + WINPR_ASSERT(subsystem->height <= INT32_MAX); + virtualScreen->right = (INT32)subsystem->width - 1; + virtualScreen->bottom = (INT32)subsystem->height - 1; + virtualScreen->flags = 1; + WLog_INFO(TAG, + "X11 Extensions: XFixes: %" PRId32 " Xinerama: %" PRId32 " XDamage: %" PRId32 + " XShm: %" PRId32 "", + subsystem->use_xfixes, subsystem->use_xinerama, subsystem->use_xdamage, + subsystem->use_xshm); + } + return 1; +} + +static int x11_shadow_subsystem_uninit(rdpShadowSubsystem* sub) +{ + x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub; + + if (!subsystem) + return -1; + + if (subsystem->display) + { + XCloseDisplay(subsystem->display); + subsystem->display = NULL; + } + + if (subsystem->common.event) + { + CloseHandle(subsystem->common.event); + subsystem->common.event = NULL; + } + + if (subsystem->cursorPixels) + { + winpr_aligned_free(subsystem->cursorPixels); + subsystem->cursorPixels = NULL; + } + + return 1; +} + +static int x11_shadow_subsystem_start(rdpShadowSubsystem* sub) +{ + x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub; + + if (!subsystem) + return -1; + + if (!(subsystem->thread = + CreateThread(NULL, 0, x11_shadow_subsystem_thread, (void*)subsystem, 0, NULL))) + { + WLog_ERR(TAG, "Failed to create thread"); + return -1; + } + + return 1; +} + +static int x11_shadow_subsystem_stop(rdpShadowSubsystem* sub) +{ + x11ShadowSubsystem* subsystem = (x11ShadowSubsystem*)sub; + + if (!subsystem) + return -1; + + if (subsystem->thread) + { + if (MessageQueue_PostQuit(subsystem->common.MsgPipe->In, 0)) + WaitForSingleObject(subsystem->thread, INFINITE); + + CloseHandle(subsystem->thread); + subsystem->thread = NULL; + } + + return 1; +} + +static rdpShadowSubsystem* x11_shadow_subsystem_new(void) +{ + x11ShadowSubsystem* subsystem = NULL; + subsystem = (x11ShadowSubsystem*)calloc(1, sizeof(x11ShadowSubsystem)); + + if (!subsystem) + return NULL; + +#ifdef WITH_PAM + subsystem->common.Authenticate = x11_shadow_pam_authenticate; +#endif + subsystem->common.SynchronizeEvent = x11_shadow_input_synchronize_event; + subsystem->common.KeyboardEvent = x11_shadow_input_keyboard_event; + subsystem->common.UnicodeKeyboardEvent = x11_shadow_input_unicode_keyboard_event; + subsystem->common.MouseEvent = x11_shadow_input_mouse_event; + subsystem->common.ExtendedMouseEvent = x11_shadow_input_extended_mouse_event; + subsystem->composite = FALSE; + subsystem->use_xshm = FALSE; /* temporarily disabled */ + subsystem->use_xfixes = TRUE; + subsystem->use_xdamage = FALSE; + subsystem->use_xinerama = TRUE; + return (rdpShadowSubsystem*)subsystem; +} + +static void x11_shadow_subsystem_free(rdpShadowSubsystem* subsystem) +{ + if (!subsystem) + return; + + x11_shadow_subsystem_uninit(subsystem); + free(subsystem); +} + +FREERDP_ENTRY_POINT(FREERDP_API const char* ShadowSubsystemName(void)) +{ + return "X11"; +} + +FREERDP_ENTRY_POINT(FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)) +{ + if (!pEntryPoints) + return -1; + + pEntryPoints->New = x11_shadow_subsystem_new; + pEntryPoints->Free = x11_shadow_subsystem_free; + pEntryPoints->Init = x11_shadow_subsystem_init; + pEntryPoints->Uninit = x11_shadow_subsystem_uninit; + pEntryPoints->Start = x11_shadow_subsystem_start; + pEntryPoints->Stop = x11_shadow_subsystem_stop; + pEntryPoints->EnumMonitors = x11_shadow_enum_monitors; + return 1; +} diff --git a/server/shadow/X11/x11_shadow.h b/server/shadow/X11/x11_shadow.h new file mode 100644 index 0000000..aca2d63 --- /dev/null +++ b/server/shadow/X11/x11_shadow.h @@ -0,0 +1,114 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_X11_H +#define FREERDP_SERVER_SHADOW_X11_H + +#include <freerdp/server/shadow.h> + +typedef struct x11_shadow_subsystem x11ShadowSubsystem; + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/stream.h> +#include <winpr/collections.h> + +#include <X11/Xlib.h> + +#ifdef WITH_XSHM +#include <X11/extensions/XShm.h> +#endif + +#ifdef WITH_XFIXES +#include <X11/extensions/Xfixes.h> +#endif + +#ifdef WITH_XTEST +#include <X11/extensions/XTest.h> +#endif + +#ifdef WITH_XDAMAGE +#include <X11/extensions/Xdamage.h> +#endif + +#ifdef WITH_XINERAMA +#include <X11/extensions/Xinerama.h> +#endif + +struct x11_shadow_subsystem +{ + rdpShadowSubsystem common; + + HANDLE thread; + + UINT32 bpp; + int xfds; + UINT32 depth; + UINT32 width; + UINT32 height; + int number; + XImage* image; + Screen* screen; + Visual* visual; + Display* display; + UINT32 scanline_pad; + BOOL composite; + + BOOL use_xshm; + BOOL use_xfixes; + BOOL use_xdamage; + BOOL use_xinerama; + + XImage* fb_image; + Pixmap fb_pixmap; + Window root_window; + XShmSegmentInfo fb_shm_info; + + UINT32 cursorHotX; + UINT32 cursorHotY; + UINT32 cursorWidth; + UINT32 cursorHeight; + UINT32 cursorId; + BYTE* cursorPixels; + UINT32 cursorMaxWidth; + UINT32 cursorMaxHeight; + rdpShadowClient* lastMouseClient; + +#ifdef WITH_XDAMAGE + GC xshm_gc; + Damage xdamage; + int xdamage_notify_event; + XserverRegion xdamage_region; +#endif + +#ifdef WITH_XFIXES + int xfixes_cursor_notify_event; +#endif +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_X11_H */ diff --git a/server/shadow/freerdp-shadow-cli.1.in b/server/shadow/freerdp-shadow-cli.1.in new file mode 100644 index 0000000..890fb7a --- /dev/null +++ b/server/shadow/freerdp-shadow-cli.1.in @@ -0,0 +1,85 @@ +.de URL +\\$2 \(laURL: \\$1 \(ra\\$3 +.. +.if \n[.g] .mso www.tmac +.TH @MANPAGE_NAME@ 1 2017-01-12 "@FREERDP_VERSION_FULL@" "FreeRDP" +.SH NAME +@MANPAGE_NAME@ \- A utility for sharing a X display via RDP. +.SH SYNOPSIS +.B @MANPAGE_NAME@ +[\fB/port:\fP\fI<port number>\fP] +[\fB/ipc-socket:\fP\fI<ipc-socket>\fP] +[\fB/monitors:\fP\fI<0,1,2,...>\fP] +[\fB/rect:\fP\fI<x,y,w,h>\fP] +[\fB+auth\fP] +[\fB-may-view\fP] +[\fB-may-interact\fP] +[\fB/sec:\fP\fI<rdp|tls|nla|ext>\fP] +[\fB-sec-rdp\fP] +[\fB-sec-tls\fP] +[\fB-sec-nla\fP] +[\fB-sec-ext\fP] +[\fB/sam-file:\fP\fI<file>\fP] +[\fB/version\fP] +[\fB/help\fP] +.SH DESCRIPTION +.B @MANPAGE_NAME@ +can be used to share a running X display like with VNC but by using the RDP +instead. It is also possibly to share only parts (rect) of the display. +.SH OPTIONS +.IP /ipc-socket:<ipc-socket> +If this option is set an ipc socket with the path \fIipc-socket\fP is used +instead of a TCP socket. +.IP /port:<port> +Set the port to use. Default is 3389. +This option is ignored if ipc-socket is used. +.IP /monitors:<1,2,3,...> +Select the monitor(s) to share. +.IP /rect:<x,y,w,h> +Select rectangle within monitor to share. +.IP -auth +Disable authentication. If authentication is enabled PAM is used with the +X11 subsystem. Running as root is not necessary, however if run as user only +the same user that started @MANPAGE_NAME@ can authenticate. +.br +\fBWarning\fP: If authentication is disabled \fIeveryone\fP can connect. +.IP -may-view +Clients may view without prompt. +.IP -may-interact +Clients may interact without prompt. +.IP /sec:<rdp|tls|nla|ext> +Force a specific protocol security +.IP -sec-rdp +Disable RDP security (default:on) +.IP -sec-tls +Disable TLS protocol security (default:on) +.IP -sec-nla +Disable NLA protocol security (default:on) +.IP +sec-ext +Use NLA extended protocol security (default:off) +.IP /sam-file:<file> +NTLM SAM file for NLA authentication +.IP /version +Print the version and exit. +.IP /help +Print the help and exit. + +.SH EXAMPLES +@MANPAGE_NAME@ /port:12345 + +When run as user within a X session (for example from an xterm) a socket on +12345 is opened and the current display is shared via RDP. + +.SH EXIT STATUS +.TP +.B 0 +Successful program execution. +.TP +.B 1 +Otherwise. + +.SH SEE ALSO +wlog(7) + +.SH AUTHOR +FreeRDP <team@freerdp.com> diff --git a/server/shadow/freerdp-shadow.pc.in b/server/shadow/freerdp-shadow.pc.in new file mode 100644 index 0000000..805e2af --- /dev/null +++ b/server/shadow/freerdp-shadow.pc.in @@ -0,0 +1,15 @@ +prefix=@PKG_CONFIG_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@FREERDP_INCLUDE_DIR@ +libs=-lfreerdp-shadow@FREERDP_API_VERSION@ -lfreerdp-shadow-subsystem@FREERDP_API_VERSION@ + +Name: FreeRDP shadow +Description: FreeRDP: A Remote Desktop Protocol Implementation +URL: http://www.freerdp.com/ +Version: @FREERDP_VERSION@ +Requires: +Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_API_VERSION@ +Libs: -L${libdir} ${libs} +Libs.private: -ldl -lpthread +Cflags: -I${includedir} diff --git a/server/shadow/shadow.c b/server/shadow/shadow.c new file mode 100644 index 0000000..14c9014 --- /dev/null +++ b/server/shadow/shadow.c @@ -0,0 +1,179 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/ssl.h> +#include <winpr/path.h> +#include <winpr/cmdline.h> +#include <winpr/winsock.h> + +#include <winpr/tools/makecert.h> + +#include <freerdp/server/shadow.h> +#include <freerdp/settings.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("shadow") + +int main(int argc, char** argv) +{ + int status = 0; + DWORD dwExitCode = 0; + COMMAND_LINE_ARGUMENT_A shadow_args[] = { + { "log-filters", COMMAND_LINE_VALUE_REQUIRED, "<tag>:<level>[,<tag>:<level>[,...]]", NULL, + NULL, -1, NULL, "Set logger filters, see wLog(7) for details" }, + { "log-level", COMMAND_LINE_VALUE_REQUIRED, "[OFF|FATAL|ERROR|WARN|INFO|DEBUG|TRACE]", NULL, + NULL, -1, NULL, "Set the default log level, see wLog(7) for details" }, + { "port", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL, "Server port" }, + { "ipc-socket", COMMAND_LINE_VALUE_REQUIRED, "<ipc-socket>", NULL, NULL, -1, NULL, + "Server IPC socket" }, + { "bind-address", COMMAND_LINE_VALUE_REQUIRED, "<bind-address>[,<another address>, ...]", + NULL, NULL, -1, NULL, + "An address to bind to. Use '[<ipv6>]' for IPv6 addresses, e.g. '[::1]' for " + "localhost" }, + { "monitors", COMMAND_LINE_VALUE_OPTIONAL, "<0,1,2...>", NULL, NULL, -1, NULL, + "Select or list monitors" }, + { "max-connections", COMMAND_LINE_VALUE_REQUIRED, "<number>", 0, NULL, -1, NULL, + "maximum connections allowed to server, 0 to deactivate" }, + { "rect", COMMAND_LINE_VALUE_REQUIRED, "<x,y,w,h>", NULL, NULL, -1, NULL, + "Select rectangle within monitor to share" }, + { "auth", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, + "Clients must authenticate" }, + { "remote-guard", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, + "Remote credential guard" }, + { "may-view", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "Clients may view without prompt" }, + { "may-interact", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "Clients may interact without prompt" }, + { "sec", COMMAND_LINE_VALUE_REQUIRED, "<rdp|tls|nla|ext>", NULL, NULL, -1, NULL, + "force specific protocol security" }, + { "sec-rdp", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "rdp protocol security" }, + { "sec-tls", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "tls protocol security" }, + { "sec-nla", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "nla protocol security" }, + { "sec-ext", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, + "nla extended protocol security" }, + { "sam-file", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL, + "NTLM SAM file for NLA authentication" }, + { "keytab", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL, + "Kerberos keytab file for NLA authentication" }, + { "ccache", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL, + "Kerberos host ccache file for NLA authentication" }, + { "tls-secrets-file", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL, + "file where tls secrets shall be stored" }, + { "gfx-progressive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "Allow GFX progressive codec" }, + { "gfx-rfx", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "Allow GFX RFX codec" }, + { "gfx-planar", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "Allow GFX planar codec" }, + { "gfx-avc420", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "Allow GFX AVC420 codec" }, + { "gfx-avc444", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, + "Allow GFX AVC444 codec" }, + { "version", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_VERSION, NULL, NULL, NULL, -1, + NULL, "Print version" }, + { "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL, + -1, NULL, "Print the build configuration" }, + { "help", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "?", + "Print help" }, + { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL } + }; + + shadow_subsystem_set_entry_builtin(NULL); + + rdpShadowServer* server = shadow_server_new(); + + if (!server) + { + status = -1; + WLog_ERR(TAG, "Server new failed"); + goto fail; + } + + rdpSettings* settings = server->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE)) + goto fail; + + /* By default allow all GFX modes. + * This can be changed with command line flags [+|-]gfx-CODEC + */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32) || + !freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GfxH264, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GfxProgressiveV2, TRUE)) + goto fail; + + /* TODO: We do not implement relative mouse callbacks, so deactivate it for now */ + if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_HasRelativeMouseEvent, FALSE)) + goto fail; + + if ((status = shadow_server_parse_command_line(server, argc, argv, shadow_args)) < 0) + { + shadow_server_command_line_status_print(server, argc, argv, status, shadow_args); + goto fail; + } + + if ((status = shadow_server_init(server)) < 0) + { + WLog_ERR(TAG, "Server initialization failed."); + goto fail; + } + + if ((status = shadow_server_start(server)) < 0) + { + WLog_ERR(TAG, "Failed to start server."); + goto fail; + } + +#ifdef _WIN32 + { + MSG msg = { 0 }; + while (GetMessage(&msg, 0, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +#endif + + WaitForSingleObject(server->thread, INFINITE); + + if (!GetExitCodeThread(server->thread, &dwExitCode)) + status = -1; + else + status = (int)dwExitCode; + +fail: + shadow_server_uninit(server); + shadow_server_free(server); + return status; +} diff --git a/server/shadow/shadow.h b/server/shadow/shadow.h new file mode 100644 index 0000000..33eb805 --- /dev/null +++ b/server/shadow/shadow.h @@ -0,0 +1,44 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_SHADOW_H +#define FREERDP_SERVER_SHADOW_SHADOW_H + +#include <freerdp/server/shadow.h> + +#include "shadow_client.h" +#include "shadow_input.h" +#include "shadow_screen.h" +#include "shadow_surface.h" +#include "shadow_encoder.h" +#include "shadow_capture.h" +#include "shadow_channels.h" +#include "shadow_subsystem.h" +#include "shadow_lobby.h" +#include "shadow_mcevent.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_SHADOW_H */ diff --git a/server/shadow/shadow_audin.c b/server/shadow/shadow_audin.c new file mode 100644 index 0000000..9e7f6ef --- /dev/null +++ b/server/shadow/shadow_audin.c @@ -0,0 +1,104 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.com> + * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de> + * + * 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 <freerdp/log.h> +#include "shadow.h" + +#include "shadow_audin.h" +#include <freerdp/server/server-common.h> + +#if defined(CHANNEL_AUDIN_SERVER) +#include <freerdp/server/audin.h> +#endif + +#define TAG SERVER_TAG("shadow") + +#if defined(CHANNEL_AUDIN_SERVER) + +static UINT AudinServerData(audin_server_context* audin, const SNDIN_DATA* data) +{ + rdpShadowClient* client = NULL; + rdpShadowSubsystem* subsystem = NULL; + + WINPR_ASSERT(audin); + WINPR_ASSERT(data); + + client = audin->userdata; + WINPR_ASSERT(client); + WINPR_ASSERT(client->server); + subsystem = client->server->subsystem; + WINPR_ASSERT(subsystem); + + if (!client->mayInteract) + return CHANNEL_RC_OK; + + if (!IFCALLRESULT(TRUE, subsystem->AudinServerReceiveSamples, subsystem, client, + audin_server_get_negotiated_format(client->audin), data->Data)) + return ERROR_INTERNAL_ERROR; + + return CHANNEL_RC_OK; +} + +#endif + +BOOL shadow_client_audin_init(rdpShadowClient* client) +{ + WINPR_ASSERT(client); + +#if defined(CHANNEL_AUDIN_SERVER) + audin_server_context* audin = client->audin = audin_server_context_new(client->vcm); + + if (!audin) + return FALSE; + + audin->userdata = client; + + audin->Data = AudinServerData; + + if (client->subsystem->audinFormats) + { + if (!audin_server_set_formats(client->audin, client->subsystem->nAudinFormats, + client->subsystem->audinFormats)) + goto fail; + } + else + { + if (!audin_server_set_formats(client->audin, -1, NULL)) + goto fail; + } + + return TRUE; +fail: + audin_server_context_free(audin); + client->audin = NULL; +#endif + return FALSE; +} + +void shadow_client_audin_uninit(rdpShadowClient* client) +{ + WINPR_ASSERT(client); + +#if defined(CHANNEL_AUDIN_SERVER) + audin_server_context_free(client->audin); + client->audin = NULL; +#endif +} diff --git a/server/shadow/shadow_audin.h b/server/shadow/shadow_audin.h new file mode 100644 index 0000000..0499987 --- /dev/null +++ b/server/shadow/shadow_audin.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_AUDIN_H +#define FREERDP_SERVER_SHADOW_AUDIN_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + BOOL shadow_client_audin_init(rdpShadowClient* client); + void shadow_client_audin_uninit(rdpShadowClient* client); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_AUDIN_H */ diff --git a/server/shadow/shadow_capture.c b/server/shadow/shadow_capture.c new file mode 100644 index 0000000..4711ded --- /dev/null +++ b/server/shadow/shadow_capture.c @@ -0,0 +1,263 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> + +#include <freerdp/log.h> + +#include "shadow_surface.h" + +#include "shadow_capture.h" + +#define TAG SERVER_TAG("shadow") + +int shadow_capture_align_clip_rect(RECTANGLE_16* rect, RECTANGLE_16* clip) +{ + int dx = 0; + int dy = 0; + dx = (rect->left % 16); + + if (dx != 0) + { + rect->left -= dx; + rect->right += dx; + } + + dx = (rect->right % 16); + + if (dx != 0) + { + rect->right += (16 - dx); + } + + dy = (rect->top % 16); + + if (dy != 0) + { + rect->top -= dy; + rect->bottom += dy; + } + + dy = (rect->bottom % 16); + + if (dy != 0) + { + rect->bottom += (16 - dy); + } + + if (rect->left < clip->left) + rect->left = clip->left; + + if (rect->top < clip->top) + rect->top = clip->top; + + if (rect->right > clip->right) + rect->right = clip->right; + + if (rect->bottom > clip->bottom) + rect->bottom = clip->bottom; + + return 1; +} + +int shadow_capture_compare(BYTE* pData1, UINT32 nStep1, UINT32 nWidth, UINT32 nHeight, BYTE* pData2, + UINT32 nStep2, RECTANGLE_16* rect) +{ + BOOL equal = 0; + BOOL allEqual = 0; + UINT32 tw = 0; + UINT32 th = 0; + UINT32 nrow = 0; + UINT32 ncol = 0; + UINT32 l = 0; + UINT32 t = 0; + UINT32 r = 0; + UINT32 b = 0; + BYTE* p1 = NULL; + BYTE* p2 = NULL; + BOOL rows[1024]; +#ifdef WITH_DEBUG_SHADOW_CAPTURE + BOOL cols[1024] = { FALSE }; +#endif + allEqual = TRUE; + ZeroMemory(rect, sizeof(RECTANGLE_16)); + FillMemory(rows, sizeof(rows), 0xFF); +#ifdef WITH_DEBUG_SHADOW_CAPTURE + FillMemory(cols, sizeof(cols), 0xFF); +#endif + nrow = (nHeight + 15) / 16; + ncol = (nWidth + 15) / 16; + l = ncol + 1; + r = 0; + t = nrow + 1; + b = 0; + + for (UINT32 ty = 0; ty < nrow; ty++) + { + th = ((ty + 1) == nrow) ? (nHeight % 16) : 16; + + if (!th) + th = 16; + + for (UINT32 tx = 0; tx < ncol; tx++) + { + equal = TRUE; + tw = ((tx + 1) == ncol) ? (nWidth % 16) : 16; + + if (!tw) + tw = 16; + + p1 = &pData1[(ty * 16 * nStep1) + (tx * 16 * 4)]; + p2 = &pData2[(ty * 16 * nStep2) + (tx * 16 * 4)]; + + for (UINT32 k = 0; k < th; k++) + { + if (memcmp(p1, p2, tw * 4) != 0) + { + equal = FALSE; + break; + } + + p1 += nStep1; + p2 += nStep2; + } + + if (!equal) + { + rows[ty] = FALSE; +#ifdef WITH_DEBUG_SHADOW_CAPTURE + cols[tx] = FALSE; +#endif + + if (l > tx) + l = tx; + + if (r < tx) + r = tx; + } + } + + if (!rows[ty]) + { + allEqual = FALSE; + + if (t > ty) + t = ty; + + if (b < ty) + b = ty; + } + } + + if (allEqual) + return 0; + + WINPR_ASSERT(l * 16 <= UINT16_MAX); + WINPR_ASSERT(t * 16 <= UINT16_MAX); + WINPR_ASSERT((r + 1) * 16 <= UINT16_MAX); + WINPR_ASSERT((b + 1) * 16 <= UINT16_MAX); + rect->left = (UINT16)l * 16; + rect->top = (UINT16)t * 16; + rect->right = (UINT16)(r + 1) * 16; + rect->bottom = (UINT16)(b + 1) * 16; + + WINPR_ASSERT(nWidth <= UINT16_MAX); + if (rect->right > nWidth) + rect->right = (UINT16)nWidth; + + WINPR_ASSERT(nHeight <= UINT16_MAX); + if (rect->bottom > nHeight) + rect->bottom = (UINT16)nHeight; + +#ifdef WITH_DEBUG_SHADOW_CAPTURE + size_t size = ncol + 1; + char* col_str = calloc(size, sizeof(char)); + + if (!col_str) + { + WLog_ERR(TAG, "calloc failed!"); + return 1; + } + + for (UINT32 tx = 0; tx < ncol; tx++) + sprintf_s(&col_str[tx], size - tx, "-"); + + WLog_INFO(TAG, "%s", col_str); + + for (UINT32 tx = 0; tx < ncol; tx++) + sprintf_s(&col_str[tx], size - tx, "%c", cols[tx] ? 'O' : 'X'); + + WLog_INFO(TAG, "%s", col_str); + + for (UINT32 tx = 0; tx < ncol; tx++) + sprintf_s(&col_str[tx], size - tx, "-"); + + WLog_INFO(TAG, "%s", col_str); + + for (UINT32 ty = 0; ty < nrow; ty++) + { + for (UINT32 tx = 0; tx < ncol; tx++) + sprintf_s(&col_str[tx], size - tx, "%c", cols[tx] ? 'O' : 'X'); + + WLog_INFO(TAG, "%s", col_str); + WLog_INFO(TAG, "|%s|", rows[ty] ? "O" : "X"); + } + + WLog_INFO(TAG, + "left: %" PRIu32 " top: %" PRIu32 " right: %" PRIu32 " bottom: %" PRIu32 + " ncol: %" PRIu32 " nrow: %" PRIu32, + l, t, r, b, ncol, nrow); + free(col_str); +#endif + return 1; +} + +rdpShadowCapture* shadow_capture_new(rdpShadowServer* server) +{ + WINPR_ASSERT(server); + + rdpShadowCapture* capture = (rdpShadowCapture*)calloc(1, sizeof(rdpShadowCapture)); + + if (!capture) + return NULL; + + capture->server = server; + + if (!InitializeCriticalSectionAndSpinCount(&(capture->lock), 4000)) + { + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + shadow_capture_free(capture); + WINPR_PRAGMA_DIAG_POP + return NULL; + } + + return capture; +} + +void shadow_capture_free(rdpShadowCapture* capture) +{ + if (!capture) + return; + + DeleteCriticalSection(&(capture->lock)); + free(capture); +} diff --git a/server/shadow/shadow_capture.h b/server/shadow/shadow_capture.h new file mode 100644 index 0000000..31de550 --- /dev/null +++ b/server/shadow/shadow_capture.h @@ -0,0 +1,52 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_CAPTURE_H +#define FREERDP_SERVER_SHADOW_CAPTURE_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/winpr.h> +#include <winpr/synch.h> + +struct rdp_shadow_capture +{ + rdpShadowServer* server; + + int width; + int height; + + CRITICAL_SECTION lock; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + + void shadow_capture_free(rdpShadowCapture* capture); + + WINPR_ATTR_MALLOC(shadow_capture_free, 1) + rdpShadowCapture* shadow_capture_new(rdpShadowServer* server); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_CAPTURE_H */ diff --git a/server/shadow/shadow_channels.c b/server/shadow/shadow_channels.c new file mode 100644 index 0000000..07c5105 --- /dev/null +++ b/server/shadow/shadow_channels.c @@ -0,0 +1,66 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include "shadow.h" + +#include "shadow_channels.h" + +UINT shadow_client_channels_post_connect(rdpShadowClient* client) +{ + if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, ENCOMSP_SVC_CHANNEL_NAME)) + { + shadow_client_encomsp_init(client); + } + + if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, REMDESK_SVC_CHANNEL_NAME)) + { + shadow_client_remdesk_init(client); + } + + if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, RDPSND_CHANNEL_NAME)) + { + shadow_client_rdpsnd_init(client); + } + + shadow_client_audin_init(client); + + if (freerdp_settings_get_bool(client->context.settings, FreeRDP_SupportGraphicsPipeline)) + { + shadow_client_rdpgfx_init(client); + } + + return CHANNEL_RC_OK; +} + +void shadow_client_channels_free(rdpShadowClient* client) +{ + if (freerdp_settings_get_bool(client->context.settings, FreeRDP_SupportGraphicsPipeline)) + { + shadow_client_rdpgfx_uninit(client); + } + + shadow_client_audin_uninit(client); + + shadow_client_rdpsnd_uninit(client); + + shadow_client_remdesk_uninit(client); + + shadow_client_encomsp_uninit(client); +} diff --git a/server/shadow/shadow_channels.h b/server/shadow/shadow_channels.h new file mode 100644 index 0000000..261041e --- /dev/null +++ b/server/shadow/shadow_channels.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_CHANNELS_H +#define FREERDP_SERVER_SHADOW_CHANNELS_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#include "shadow_encomsp.h" +#include "shadow_remdesk.h" +#include "shadow_rdpsnd.h" +#include "shadow_audin.h" +#include "shadow_rdpgfx.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + UINT shadow_client_channels_post_connect(rdpShadowClient* client); + void shadow_client_channels_free(rdpShadowClient* client); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_CHANNELS_H */ diff --git a/server/shadow/shadow_client.c b/server/shadow/shadow_client.c new file mode 100644 index 0000000..0fd5236 --- /dev/null +++ b/server/shadow/shadow_client.c @@ -0,0 +1,2612 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 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/assert.h> +#include <winpr/file.h> +#include <winpr/path.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/sysinfo.h> +#include <winpr/interlocked.h> + +#include <freerdp/log.h> +#include <freerdp/channels/drdynvc.h> + +#include "shadow.h" + +#define TAG CLIENT_TAG("shadow") + +typedef struct +{ + BOOL gfxOpened; + BOOL gfxSurfaceCreated; +} SHADOW_GFX_STATUS; + +static INLINE BOOL shadow_client_rdpgfx_new_surface(rdpShadowClient* client) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_CREATE_SURFACE_PDU createSurface; + RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU surfaceToOutput; + RdpgfxServerContext* context = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + context = client->rdpgfx; + WINPR_ASSERT(context); + settings = ((rdpContext*)client)->settings; + WINPR_ASSERT(settings); + + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX); + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX); + createSurface.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + createSurface.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + createSurface.pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888; + createSurface.surfaceId = client->surfaceId; + surfaceToOutput.outputOriginX = 0; + surfaceToOutput.outputOriginY = 0; + surfaceToOutput.surfaceId = client->surfaceId; + surfaceToOutput.reserved = 0; + IFCALLRET(context->CreateSurface, error, context, &createSurface); + + if (error) + { + WLog_ERR(TAG, "CreateSurface failed with error %" PRIu32 "", error); + return FALSE; + } + + IFCALLRET(context->MapSurfaceToOutput, error, context, &surfaceToOutput); + + if (error) + { + WLog_ERR(TAG, "MapSurfaceToOutput failed with error %" PRIu32 "", error); + return FALSE; + } + + return TRUE; +} + +static INLINE BOOL shadow_client_rdpgfx_release_surface(rdpShadowClient* client) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_DELETE_SURFACE_PDU pdu; + RdpgfxServerContext* context = NULL; + + WINPR_ASSERT(client); + + context = client->rdpgfx; + WINPR_ASSERT(context); + + pdu.surfaceId = client->surfaceId++; + IFCALLRET(context->DeleteSurface, error, context, &pdu); + + if (error) + { + WLog_ERR(TAG, "DeleteSurface failed with error %" PRIu32 "", error); + return FALSE; + } + + return TRUE; +} + +static INLINE BOOL shadow_client_rdpgfx_reset_graphic(rdpShadowClient* client) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_RESET_GRAPHICS_PDU pdu = { 0 }; + RdpgfxServerContext* context = NULL; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->rdpgfx); + + context = client->rdpgfx; + WINPR_ASSERT(context); + + settings = client->context.settings; + WINPR_ASSERT(settings); + + pdu.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + pdu.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + pdu.monitorCount = client->subsystem->numMonitors; + pdu.monitorDefArray = client->subsystem->monitors; + IFCALLRET(context->ResetGraphics, error, context, &pdu); + + if (error) + { + WLog_ERR(TAG, "ResetGraphics failed with error %" PRIu32 "", error); + return FALSE; + } + + client->first_frame = TRUE; + return TRUE; +} + +static INLINE void shadow_client_free_queued_message(void* obj) +{ + wMessage* message = (wMessage*)obj; + + WINPR_ASSERT(message); + if (message->Free) + { + message->Free(message); + message->Free = NULL; + } +} + +static void shadow_client_context_free(freerdp_peer* peer, rdpContext* context) +{ + rdpShadowClient* client = (rdpShadowClient*)context; + rdpShadowServer* server = NULL; + + WINPR_UNUSED(peer); + if (!client) + return; + + server = client->server; + if (server && server->clients) + ArrayList_Remove(server->clients, (void*)client); + + shadow_encoder_free(client->encoder); + + /* Clear queued messages and free resource */ + MessageQueue_Free(client->MsgQueue); + WTSCloseServer((HANDLE)client->vcm); + region16_uninit(&(client->invalidRegion)); + DeleteCriticalSection(&(client->lock)); + + client->MsgQueue = NULL; + client->encoder = NULL; + client->vcm = NULL; +} + +static BOOL shadow_client_context_new(freerdp_peer* peer, rdpContext* context) +{ + BOOL NSCodec = 0; + const char bind_address[] = "bind-address,"; + rdpShadowClient* client = (rdpShadowClient*)context; + rdpSettings* settings = NULL; + const rdpSettings* srvSettings = NULL; + rdpShadowServer* server = NULL; + const wObject cb = { NULL, NULL, NULL, shadow_client_free_queued_message, NULL }; + + WINPR_ASSERT(client); + WINPR_ASSERT(peer); + WINPR_ASSERT(peer->context); + + server = (rdpShadowServer*)peer->ContextExtra; + WINPR_ASSERT(server); + + srvSettings = server->settings; + WINPR_ASSERT(srvSettings); + + client->surfaceId = 1; + client->server = server; + client->subsystem = server->subsystem; + WINPR_ASSERT(client->subsystem); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, + freerdp_settings_get_uint32(srvSettings, FreeRDP_ColorDepth))) + return FALSE; + NSCodec = freerdp_settings_get_bool(srvSettings, FreeRDP_NSCodec); + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, NSCodec)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, + freerdp_settings_get_bool(srvSettings, FreeRDP_RemoteFxCodec))) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, + freerdp_settings_get_bool(srvSettings, FreeRDP_GfxH264))) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowSkipAlpha, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowColorSubsampling, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DrawAllowDynamicColorFidelity, TRUE)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8)) + return FALSE; + + if (server->ipcSocket && (strncmp(bind_address, server->ipcSocket, + strnlen(bind_address, sizeof(bind_address))) != 0)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_LyncRdpMode, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE)) + return FALSE; + } + + client->inLobby = TRUE; + client->mayView = server->mayView; + client->mayInteract = server->mayInteract; + + if (!InitializeCriticalSectionAndSpinCount(&(client->lock), 4000)) + goto fail; + + region16_init(&(client->invalidRegion)); + client->vcm = WTSOpenServerA(peer->context); + + if (!client->vcm || client->vcm == INVALID_HANDLE_VALUE) + goto fail; + + if (!(client->MsgQueue = MessageQueue_New(&cb))) + goto fail; + + if (!(client->encoder = shadow_encoder_new(client))) + goto fail; + + if (!ArrayList_Append(server->clients, (void*)client)) + goto fail; + + return TRUE; + +fail: + shadow_client_context_free(peer, context); + return FALSE; +} + +static INLINE void shadow_client_mark_invalid(rdpShadowClient* client, UINT32 numRects, + const RECTANGLE_16* rects) +{ + RECTANGLE_16 screenRegion; + rdpSettings* settings = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(rects || (numRects == 0)); + + settings = client->context.settings; + WINPR_ASSERT(settings); + + EnterCriticalSection(&(client->lock)); + + /* Mark client invalid region. No rectangle means full screen */ + if (numRects > 0) + { + for (UINT32 index = 0; index < numRects; index++) + { + region16_union_rect(&(client->invalidRegion), &(client->invalidRegion), &rects[index]); + } + } + else + { + screenRegion.left = 0; + screenRegion.top = 0; + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX); + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX); + screenRegion.right = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + screenRegion.bottom = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + region16_union_rect(&(client->invalidRegion), &(client->invalidRegion), &screenRegion); + } + + LeaveCriticalSection(&(client->lock)); +} + +/** + * Function description + * Recalculate client desktop size and update to rdpSettings + * + * @return TRUE if width/height changed. + */ +static INLINE BOOL shadow_client_recalc_desktop_size(rdpShadowClient* client) +{ + INT32 width = 0; + INT32 height = 0; + rdpShadowServer* server = NULL; + rdpSettings* settings = NULL; + RECTANGLE_16 viewport = { 0 }; + + WINPR_ASSERT(client); + server = client->server; + settings = client->context.settings; + + WINPR_ASSERT(server); + WINPR_ASSERT(server->surface); + WINPR_ASSERT(settings); + + WINPR_ASSERT(server->surface->width <= UINT16_MAX); + WINPR_ASSERT(server->surface->height <= UINT16_MAX); + viewport.right = (UINT16)server->surface->width; + viewport.bottom = (UINT16)server->surface->height; + + if (server->shareSubRect) + { + rectangles_intersection(&viewport, &(server->subRect), &viewport); + } + + width = viewport.right - viewport.left; + height = viewport.bottom - viewport.top; + + WINPR_ASSERT(width >= 0); + WINPR_ASSERT(width <= UINT16_MAX); + WINPR_ASSERT(height >= 0); + WINPR_ASSERT(height <= UINT16_MAX); + if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) != (UINT32)width || + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) != (UINT32)height) + return TRUE; + + return FALSE; +} + +static BOOL shadow_client_capabilities(freerdp_peer* peer) +{ + rdpShadowSubsystem* subsystem = NULL; + rdpShadowClient* client = NULL; + BOOL ret = TRUE; + + WINPR_ASSERT(peer); + + client = (rdpShadowClient*)peer->context; + WINPR_ASSERT(client); + WINPR_ASSERT(client->server); + + subsystem = client->server->subsystem; + WINPR_ASSERT(subsystem); + + IFCALLRET(subsystem->ClientCapabilities, ret, subsystem, client); + + if (!ret) + WLog_WARN(TAG, "subsystem->ClientCapabilities failed"); + + return ret; +} + +static void shadow_reset_desktop_resize(rdpShadowClient* client) +{ + WINPR_ASSERT(client); + client->resizeRequested = FALSE; +} + +static BOOL shadow_send_desktop_resize(rdpShadowClient* client) +{ + BOOL rc = 0; + rdpUpdate* update = NULL; + rdpSettings* settings = NULL; + const freerdp_peer* peer = NULL; + + WINPR_ASSERT(client); + + settings = client->context.settings; + peer = client->context.peer; + WINPR_ASSERT(peer); + WINPR_ASSERT(client->server); + WINPR_ASSERT(client->server->surface); + + const UINT32 resizeWidth = client->server->surface->width; + const UINT32 resizeHeight = client->server->surface->height; + + if (client->resizeRequested) + { + if ((resizeWidth == client->resizeWidth) && (resizeHeight == client->resizeHeight)) + { + const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + WLog_WARN(TAG, + "detected previous resize request for resolution %" PRIu32 "x%" PRIu32 + ", still have %" PRIu32 "x%" PRIu32 ", disconnecting peer", + resizeWidth, resizeHeight, w, h); + return FALSE; + } + } + + update = client->context.update; + WINPR_ASSERT(update); + WINPR_ASSERT(update->DesktopResize); + + // Update peer resolution, required so that during disconnect/reconnect the correct resolution + // is sent to the client. + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, resizeWidth)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, resizeHeight)) + return FALSE; + rc = update->DesktopResize(update->context); + WLog_INFO(TAG, "Client %s resize requested (%" PRIu32 "x%" PRIu32 "@%" PRIu32 ")", + peer->hostname, resizeWidth, resizeHeight, + freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)); + client->resizeRequested = TRUE; + client->resizeWidth = resizeWidth; + client->resizeHeight = resizeHeight; + + return rc; +} + +static BOOL shadow_client_post_connect(freerdp_peer* peer) +{ + int authStatus = 0; + rdpSettings* settings = NULL; + rdpShadowClient* client = NULL; + rdpShadowServer* server = NULL; + rdpShadowSubsystem* subsystem = NULL; + + WINPR_ASSERT(peer); + + client = (rdpShadowClient*)peer->context; + WINPR_ASSERT(client); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + server = client->server; + WINPR_ASSERT(server); + + subsystem = server->subsystem; + WINPR_ASSERT(subsystem); + + if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) == 24) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 16)) /* disable 24bpp */ + return FALSE; + } + + const UINT32 MultifragMaxRequestSize = + freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize); + if (MultifragMaxRequestSize < 0x3F0000) + { + BOOL rc = freerdp_settings_set_bool( + settings, FreeRDP_NSCodec, + FALSE); /* NSCodec compressor does not support fragmentation yet */ + WINPR_ASSERT(rc); + } + + WLog_INFO(TAG, "Client from %s is activated (%" PRIu32 "x%" PRIu32 "@%" PRIu32 ")", + peer->hostname, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)); + + if (shadow_client_channels_post_connect(client) != CHANNEL_RC_OK) + return FALSE; + + shadow_client_mark_invalid(client, 0, NULL); + authStatus = -1; + + const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username); + const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain); + const char* Password = freerdp_settings_get_string(settings, FreeRDP_Password); + + if (Username && Password) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled, TRUE)) + return FALSE; + } + + if (server->authentication && !freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity)) + { + if (subsystem->Authenticate) + { + authStatus = subsystem->Authenticate(subsystem, client, Username, Domain, Password); + } + + if (authStatus < 0) + { + WLog_ERR(TAG, "client authentication failure: %d", authStatus); + return FALSE; + } + } + + if (subsystem->ClientConnect) + { + return subsystem->ClientConnect(subsystem, client); + } + + return TRUE; +} + +/* Convert rects in sub rect coordinate to client/surface coordinate */ +static INLINE void shadow_client_convert_rects(rdpShadowClient* client, RECTANGLE_16* dst, + const RECTANGLE_16* src, UINT32 numRects) +{ + WINPR_ASSERT(client); + WINPR_ASSERT(client->server); + WINPR_ASSERT(dst); + WINPR_ASSERT(src || (numRects == 0)); + + if (client->server->shareSubRect) + { + UINT16 offsetX = client->server->subRect.left; + UINT16 offsetY = client->server->subRect.top; + + for (UINT32 i = 0; i < numRects; i++) + { + const RECTANGLE_16* s = &src[i]; + RECTANGLE_16* d = &dst[i]; + + d->left = s->left + offsetX; + d->right = s->right + offsetX; + d->top = s->top + offsetY; + d->bottom = s->bottom + offsetY; + } + } + else + { + if (src != dst) + { + CopyMemory(dst, src, numRects * sizeof(RECTANGLE_16)); + } + } +} + +static BOOL shadow_client_refresh_request(rdpShadowClient* client) +{ + wMessage message = { 0 }; + wMessagePipe* MsgPipe = NULL; + + WINPR_ASSERT(client); + WINPR_ASSERT(client->subsystem); + + MsgPipe = client->subsystem->MsgPipe; + WINPR_ASSERT(MsgPipe); + + message.id = SHADOW_MSG_IN_REFRESH_REQUEST_ID; + message.wParam = NULL; + message.lParam = NULL; + message.context = (void*)client; + message.Free = NULL; + return MessageQueue_Dispatch(MsgPipe->In, &message); +} + +static BOOL shadow_client_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas) +{ + rdpShadowClient* client = (rdpShadowClient*)context; + RECTANGLE_16* rects = NULL; + + /* It is invalid if we have area count but no actual area */ + if (count && !areas) + return FALSE; + + if (count) + { + rects = (RECTANGLE_16*)calloc(count, sizeof(RECTANGLE_16)); + + if (!rects) + { + return FALSE; + } + + shadow_client_convert_rects(client, rects, areas, count); + shadow_client_mark_invalid(client, count, rects); + free(rects); + } + else + { + shadow_client_mark_invalid(client, 0, NULL); + } + + return shadow_client_refresh_request(client); +} + +static BOOL shadow_client_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area) +{ + rdpShadowClient* client = (rdpShadowClient*)context; + RECTANGLE_16 region; + + WINPR_ASSERT(client); + + client->suppressOutput = allow ? FALSE : TRUE; + + if (allow) + { + if (area) + { + shadow_client_convert_rects(client, ®ion, area, 1); + shadow_client_mark_invalid(client, 1, ®ion); + } + else + { + shadow_client_mark_invalid(client, 0, NULL); + } + } + + return shadow_client_refresh_request(client); +} + +static BOOL shadow_client_activate(freerdp_peer* peer) +{ + rdpSettings* settings = NULL; + rdpShadowClient* client = NULL; + + WINPR_ASSERT(peer); + + client = (rdpShadowClient*)peer->context; + WINPR_ASSERT(client); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + /* Resize client if necessary */ + if (shadow_client_recalc_desktop_size(client)) + return shadow_send_desktop_resize(client); + + shadow_reset_desktop_resize(client); + client->activated = TRUE; + client->inLobby = client->mayView ? FALSE : TRUE; + + if (shadow_encoder_reset(client->encoder) < 0) + { + WLog_ERR(TAG, "Failed to reset encoder"); + return FALSE; + } + + /* Update full screen in next update */ + return shadow_client_refresh_rect(&client->context, 0, NULL); +} + +static BOOL shadow_client_logon(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity, + BOOL automatic) +{ + BOOL rc = FALSE; + char* user = NULL; + char* domain = NULL; + char* password = NULL; + rdpSettings* settings = NULL; + + WINPR_UNUSED(automatic); + + WINPR_ASSERT(peer); + WINPR_ASSERT(identity); + + WINPR_ASSERT(peer->context); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + if (identity->Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE) + { + if (identity->User) + user = ConvertWCharNToUtf8Alloc(identity->User, identity->UserLength, NULL); + + if (identity->Domain) + domain = ConvertWCharNToUtf8Alloc(identity->Domain, identity->DomainLength, NULL); + + if (identity->Password) + password = ConvertWCharNToUtf8Alloc(identity->Password, identity->PasswordLength, NULL); + } + else + { + if (identity->User) + user = _strdup((char*)identity->User); + + if (identity->Domain) + domain = _strdup((char*)identity->Domain); + + if (identity->Password) + password = _strdup((char*)identity->Password); + } + + if ((identity->User && !user) || (identity->Domain && !domain) || + (identity->Password && !password)) + goto fail; + + if (user) + freerdp_settings_set_string(settings, FreeRDP_Username, user); + + if (domain) + freerdp_settings_set_string(settings, FreeRDP_Domain, domain); + + if (password) + freerdp_settings_set_string(settings, FreeRDP_Password, password); + + rc = TRUE; +fail: + free(user); + free(domain); + free(password); + return rc; +} + +static INLINE void shadow_client_common_frame_acknowledge(rdpShadowClient* client, UINT32 frameId) +{ + /* + * Record the last client acknowledged frame id to + * calculate how much frames are in progress. + * Some rdp clients (win7 mstsc) skips frame ACK if it is + * inactive, we should not expect ACK for each frame. + * So it is OK to calculate inflight frame count according to + * a latest acknowledged frame id. + */ + WINPR_ASSERT(client); + WINPR_ASSERT(client->encoder); + client->encoder->lastAckframeId = frameId; +} + +static BOOL shadow_client_surface_frame_acknowledge(rdpContext* context, UINT32 frameId) +{ + rdpShadowClient* client = (rdpShadowClient*)context; + shadow_client_common_frame_acknowledge(client, frameId); + /* + * Reset queueDepth for legacy none RDPGFX acknowledge + */ + WINPR_ASSERT(client); + WINPR_ASSERT(client->encoder); + client->encoder->queueDepth = QUEUE_DEPTH_UNAVAILABLE; + return TRUE; +} + +static UINT +shadow_client_rdpgfx_frame_acknowledge(RdpgfxServerContext* context, + const RDPGFX_FRAME_ACKNOWLEDGE_PDU* frameAcknowledge) +{ + rdpShadowClient* client = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(frameAcknowledge); + + client = (rdpShadowClient*)context->custom; + shadow_client_common_frame_acknowledge(client, frameAcknowledge->frameId); + + WINPR_ASSERT(client); + WINPR_ASSERT(client->encoder); + client->encoder->queueDepth = frameAcknowledge->queueDepth; + return CHANNEL_RC_OK; +} + +static BOOL shadow_are_caps_filtered(const rdpSettings* settings, UINT32 caps) +{ + const UINT32 capList[] = { RDPGFX_CAPVERSION_8, RDPGFX_CAPVERSION_81, + RDPGFX_CAPVERSION_10, RDPGFX_CAPVERSION_101, + RDPGFX_CAPVERSION_102, RDPGFX_CAPVERSION_103, + RDPGFX_CAPVERSION_104, RDPGFX_CAPVERSION_105, + RDPGFX_CAPVERSION_106, RDPGFX_CAPVERSION_106_ERR, + RDPGFX_CAPVERSION_107 }; + + WINPR_ASSERT(settings); + const UINT32 filter = freerdp_settings_get_uint32(settings, FreeRDP_GfxCapsFilter); + + for (UINT32 x = 0; x < ARRAYSIZE(capList); x++) + { + if (caps == capList[x]) + return (filter & (1 << x)) != 0; + } + + return TRUE; +} + +static UINT shadow_client_send_caps_confirm(RdpgfxServerContext* context, rdpShadowClient* client, + const RDPGFX_CAPS_CONFIRM_PDU* pdu) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(client); + WINPR_ASSERT(pdu); + + WINPR_ASSERT(context->CapsConfirm); + UINT rc = context->CapsConfirm(context, pdu); + client->areGfxCapsReady = (rc == CHANNEL_RC_OK); + return rc; +} + +static BOOL shadow_client_caps_test_version(RdpgfxServerContext* context, rdpShadowClient* client, + BOOL h264, const RDPGFX_CAPSET* capsSets, + UINT32 capsSetCount, UINT32 capsVersion, UINT* rc) +{ + const rdpSettings* srvSettings = NULL; + rdpSettings* clientSettings = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(client); + WINPR_ASSERT(capsSets || (capsSetCount == 0)); + WINPR_ASSERT(rc); + + WINPR_ASSERT(context->rdpcontext); + srvSettings = context->rdpcontext->settings; + WINPR_ASSERT(srvSettings); + + clientSettings = client->context.settings; + WINPR_ASSERT(clientSettings); + + if (shadow_are_caps_filtered(srvSettings, capsVersion)) + return FALSE; + + for (UINT32 index = 0; index < capsSetCount; index++) + { + const RDPGFX_CAPSET* currentCaps = &capsSets[index]; + + if (currentCaps->version == capsVersion) + { + UINT32 flags = 0; + BOOL planar = FALSE; + BOOL rfx = FALSE; + BOOL avc444v2 = FALSE; + BOOL avc444 = FALSE; + BOOL avc420 = FALSE; + BOOL progressive = FALSE; + RDPGFX_CAPSET caps = *currentCaps; + RDPGFX_CAPS_CONFIRM_PDU pdu = { 0 }; + pdu.capsSet = ∩︀ + + flags = pdu.capsSet->flags; + + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache, + (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE)) + return FALSE; + + avc444v2 = avc444 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED); + if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxAVC444v2) || !h264) + avc444v2 = FALSE; + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, avc444v2)) + return FALSE; + if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxAVC444) || !h264) + avc444 = FALSE; + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, avc444)) + return FALSE; + if (!freerdp_settings_get_bool(srvSettings, FreeRDP_GfxH264) || !h264) + avc420 = FALSE; + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, avc420)) + return FALSE; + + progressive = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxProgressive); + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxProgressive, progressive)) + return FALSE; + progressive = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxProgressiveV2); + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxProgressiveV2, progressive)) + return FALSE; + + rfx = freerdp_settings_get_bool(srvSettings, FreeRDP_RemoteFxCodec); + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_RemoteFxCodec, rfx)) + return FALSE; + + planar = freerdp_settings_get_bool(srvSettings, FreeRDP_GfxPlanar); + if (!freerdp_settings_set_bool(clientSettings, FreeRDP_GfxPlanar, planar)) + return FALSE; + + if (!avc444v2 && !avc444 && !avc420) + pdu.capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED; + + *rc = shadow_client_send_caps_confirm(context, client, &pdu); + return TRUE; + } + } + + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context, + const RDPGFX_CAPS_ADVERTISE_PDU* capsAdvertise) +{ + UINT rc = ERROR_INTERNAL_ERROR; + const rdpSettings* srvSettings = NULL; + rdpSettings* clientSettings = NULL; + BOOL h264 = FALSE; + + UINT32 flags = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(capsAdvertise); + + rdpShadowClient* client = (rdpShadowClient*)context->custom; + WINPR_ASSERT(client); + WINPR_ASSERT(context->rdpcontext); + + srvSettings = context->rdpcontext->settings; + WINPR_ASSERT(srvSettings); + + clientSettings = client->context.settings; + WINPR_ASSERT(clientSettings); + +#ifdef WITH_GFX_H264 + h264 = + (shadow_encoder_prepare(client->encoder, FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444) >= 0); +#else + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE); +#endif + + /* Request full screen update for new gfx channel */ + if (!shadow_client_refresh_rect(&client->context, 0, NULL)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_107, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_106, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_106_ERR, + &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_105, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_104, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_103, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_102, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_101, &rc)) + return rc; + + if (shadow_client_caps_test_version(context, client, h264, capsAdvertise->capsSets, + capsAdvertise->capsSetCount, RDPGFX_CAPVERSION_10, &rc)) + return rc; + + if (!shadow_are_caps_filtered(srvSettings, RDPGFX_CAPVERSION_81)) + { + for (UINT32 index = 0; index < capsAdvertise->capsSetCount; index++) + { + const RDPGFX_CAPSET* currentCaps = &capsAdvertise->capsSets[index]; + + if (currentCaps->version == RDPGFX_CAPVERSION_81) + { + RDPGFX_CAPSET caps = *currentCaps; + RDPGFX_CAPS_CONFIRM_PDU pdu; + pdu.capsSet = ∩︀ + + flags = pdu.capsSet->flags; + + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE); + + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxThinClient, + (flags & RDPGFX_CAPS_FLAG_THINCLIENT) ? TRUE : FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache, + (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE); + +#ifndef WITH_GFX_H264 + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE); + pdu.capsSet->flags &= ~RDPGFX_CAPS_FLAG_AVC420_ENABLED; +#else + + if (h264) + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, + (flags & RDPGFX_CAPS_FLAG_AVC420_ENABLED) ? TRUE + : FALSE); + else + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE); +#endif + + return shadow_client_send_caps_confirm(context, client, &pdu); + } + } + } + + if (!shadow_are_caps_filtered(srvSettings, RDPGFX_CAPVERSION_8)) + { + for (UINT32 index = 0; index < capsAdvertise->capsSetCount; index++) + { + const RDPGFX_CAPSET* currentCaps = &capsAdvertise->capsSets[index]; + + if (currentCaps->version == RDPGFX_CAPVERSION_8) + { + RDPGFX_CAPSET caps = *currentCaps; + RDPGFX_CAPS_CONFIRM_PDU pdu; + pdu.capsSet = ∩︀ + flags = pdu.capsSet->flags; + + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444v2, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxAVC444, FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxH264, FALSE); + + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxThinClient, + (flags & RDPGFX_CAPS_FLAG_THINCLIENT) ? TRUE : FALSE); + freerdp_settings_set_bool(clientSettings, FreeRDP_GfxSmallCache, + (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE) ? TRUE : FALSE); + + return shadow_client_send_caps_confirm(context, client, &pdu); + } + } + } + + return CHANNEL_RC_UNSUPPORTED_VERSION; +} + +static INLINE UINT32 rdpgfx_estimate_h264_avc420(RDPGFX_AVC420_BITMAP_STREAM* havc420) +{ + /* H264 metadata + H264 stream. See rdpgfx_write_h264_avc420 */ + WINPR_ASSERT(havc420); + return sizeof(UINT32) /* numRegionRects */ + + 10 /* regionRects + quantQualityVals */ + * havc420->meta.numRegionRects + + havc420->length; +} + +/** + * Function description + * + * @return TRUE on success + */ +static BOOL shadow_client_send_surface_gfx(rdpShadowClient* client, const BYTE* pSrcData, + UINT32 nSrcStep, UINT32 SrcFormat, UINT16 nXSrc, + UINT16 nYSrc, UINT16 nWidth, UINT16 nHeight) +{ + UINT32 id = 0; + UINT error = CHANNEL_RC_OK; + const rdpContext* context = (const rdpContext*)client; + const rdpSettings* settings = NULL; + rdpShadowEncoder* encoder = NULL; + RDPGFX_SURFACE_COMMAND cmd = { 0 }; + RDPGFX_START_FRAME_PDU cmdstart = { 0 }; + RDPGFX_END_FRAME_PDU cmdend = { 0 }; + SYSTEMTIME sTime = { 0 }; + + if (!context || !pSrcData) + return FALSE; + + settings = context->settings; + encoder = client->encoder; + + if (!settings || !encoder) + return FALSE; + + if (client->first_frame) + { + rfx_context_reset(encoder->rfx, nWidth, nHeight); + client->first_frame = FALSE; + } + + cmdstart.frameId = shadow_encoder_create_frame_id(encoder); + GetSystemTime(&sTime); + cmdstart.timestamp = (UINT32)(sTime.wHour << 22U | sTime.wMinute << 16U | sTime.wSecond << 10U | + sTime.wMilliseconds); + cmdend.frameId = cmdstart.frameId; + cmd.surfaceId = client->surfaceId; + cmd.format = PIXEL_FORMAT_BGRX32; + cmd.left = nXSrc; + cmd.top = nYSrc; + cmd.right = cmd.left + nWidth; + cmd.bottom = cmd.top + nHeight; + cmd.width = nWidth; + cmd.height = nHeight; + + id = freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId); +#ifdef WITH_GFX_H264 + const BOOL GfxH264 = freerdp_settings_get_bool(settings, FreeRDP_GfxH264); + const BOOL GfxAVC444 = freerdp_settings_get_bool(settings, FreeRDP_GfxAVC444); + const BOOL GfxAVC444v2 = freerdp_settings_get_bool(settings, FreeRDP_GfxAVC444v2); + if (GfxAVC444 || GfxAVC444v2) + { + INT32 rc = 0; + RDPGFX_AVC444_BITMAP_STREAM avc444 = { 0 }; + RECTANGLE_16 regionRect = { 0 }; + BYTE version = GfxAVC444v2 ? 2 : 1; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC444) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC444"); + return FALSE; + } + + WINPR_ASSERT(cmd.left <= UINT16_MAX); + WINPR_ASSERT(cmd.top <= UINT16_MAX); + WINPR_ASSERT(cmd.right <= UINT16_MAX); + WINPR_ASSERT(cmd.bottom <= UINT16_MAX); + regionRect.left = (UINT16)cmd.left; + regionRect.top = (UINT16)cmd.top; + regionRect.right = (UINT16)cmd.right; + regionRect.bottom = (UINT16)cmd.bottom; + rc = avc444_compress(encoder->h264, pSrcData, cmd.format, nSrcStep, nWidth, nHeight, + version, ®ionRect, &avc444.LC, &avc444.bitstream[0].data, + &avc444.bitstream[0].length, &avc444.bitstream[1].data, + &avc444.bitstream[1].length, &avc444.bitstream[0].meta, + &avc444.bitstream[1].meta); + if (rc < 0) + { + WLog_ERR(TAG, "avc420_compress failed for avc444"); + return FALSE; + } + + /* rc > 0 means new data */ + if (rc > 0) + { + avc444.cbAvc420EncodedBitstream1 = rdpgfx_estimate_h264_avc420(&avc444.bitstream[0]); + cmd.codecId = GfxAVC444v2 ? RDPGFX_CODECID_AVC444v2 : RDPGFX_CODECID_AVC444; + cmd.extra = (void*)&avc444; + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + } + + free_h264_metablock(&avc444.bitstream[0].meta); + free_h264_metablock(&avc444.bitstream[1].meta); + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else if (GfxH264) + { + INT32 rc = 0; + RDPGFX_AVC420_BITMAP_STREAM avc420 = { 0 }; + RECTANGLE_16 regionRect; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC420) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC420"); + return FALSE; + } + + WINPR_ASSERT(cmd.left <= UINT16_MAX); + WINPR_ASSERT(cmd.top <= UINT16_MAX); + WINPR_ASSERT(cmd.right <= UINT16_MAX); + WINPR_ASSERT(cmd.bottom <= UINT16_MAX); + regionRect.left = (UINT16)cmd.left; + regionRect.top = (UINT16)cmd.top; + regionRect.right = (UINT16)cmd.right; + regionRect.bottom = (UINT16)cmd.bottom; + rc = avc420_compress(encoder->h264, pSrcData, cmd.format, nSrcStep, nWidth, nHeight, + ®ionRect, &avc420.data, &avc420.length, &avc420.meta); + if (rc < 0) + { + WLog_ERR(TAG, "avc420_compress failed"); + return FALSE; + } + + /* rc > 0 means new data */ + if (rc > 0) + { + cmd.codecId = RDPGFX_CODECID_AVC420; + cmd.extra = (void*)&avc420; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + } + free_h264_metablock(&avc420.meta); + + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else +#endif + if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && (id != 0)) + { + BOOL rc = 0; + wStream* s = NULL; + RFX_RECT rect; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_REMOTEFX) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_REMOTEFX"); + return FALSE; + } + + s = Stream_New(NULL, 1024); + WINPR_ASSERT(s); + + WINPR_ASSERT(cmd.left <= UINT16_MAX); + WINPR_ASSERT(cmd.top <= UINT16_MAX); + WINPR_ASSERT(cmd.right <= UINT16_MAX); + WINPR_ASSERT(cmd.bottom <= UINT16_MAX); + rect.x = (UINT16)cmd.left; + rect.y = (UINT16)cmd.top; + rect.width = (UINT16)cmd.right - cmd.left; + rect.height = (UINT16)cmd.bottom - cmd.top; + + rc = rfx_compose_message(encoder->rfx, s, &rect, 1, pSrcData, nWidth, nHeight, nSrcStep); + + if (!rc) + { + WLog_ERR(TAG, "rfx_compose_message failed"); + Stream_Free(s, TRUE); + return FALSE; + } + + /* rc > 0 means new data */ + if (rc > 0) + { + const size_t pos = Stream_GetPosition(s); + WINPR_ASSERT(pos <= UINT32_MAX); + + cmd.codecId = RDPGFX_CODECID_CAVIDEO; + cmd.data = Stream_Buffer(s); + cmd.length = (UINT32)pos; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + } + + Stream_Free(s, TRUE); + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else if (freerdp_settings_get_bool(settings, FreeRDP_GfxProgressive)) + { + INT32 rc = 0; + REGION16 region; + RECTANGLE_16 regionRect; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PROGRESSIVE) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PROGRESSIVE"); + return FALSE; + } + + WINPR_ASSERT(cmd.left <= UINT16_MAX); + WINPR_ASSERT(cmd.top <= UINT16_MAX); + WINPR_ASSERT(cmd.right <= UINT16_MAX); + WINPR_ASSERT(cmd.bottom <= UINT16_MAX); + regionRect.left = (UINT16)cmd.left; + regionRect.top = (UINT16)cmd.top; + regionRect.right = (UINT16)cmd.right; + regionRect.bottom = (UINT16)cmd.bottom; + region16_init(®ion); + region16_union_rect(®ion, ®ion, ®ionRect); + rc = progressive_compress(encoder->progressive, pSrcData, nSrcStep * nHeight, cmd.format, + nWidth, nHeight, nSrcStep, ®ion, &cmd.data, &cmd.length); + region16_uninit(®ion); + if (rc < 0) + { + WLog_ERR(TAG, "progressive_compress failed"); + return FALSE; + } + + /* rc > 0 means new data */ + if (rc > 0) + { + cmd.codecId = RDPGFX_CODECID_CAPROGRESSIVE; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + } + + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else if (freerdp_settings_get_bool(settings, FreeRDP_GfxPlanar)) + { + BOOL rc = 0; + const UINT32 w = cmd.right - cmd.left; + const UINT32 h = cmd.bottom - cmd.top; + const BYTE* src = + &pSrcData[cmd.top * nSrcStep + cmd.left * FreeRDPGetBytesPerPixel(SrcFormat)]; + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PLANAR) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PLANAR"); + return FALSE; + } + + rc = freerdp_bitmap_planar_context_reset(encoder->planar, w, h); + WINPR_ASSERT(rc); + freerdp_planar_topdown_image(encoder->planar, TRUE); + + cmd.data = freerdp_bitmap_compress_planar(encoder->planar, src, SrcFormat, w, h, nSrcStep, + NULL, &cmd.length); + WINPR_ASSERT(cmd.data || (cmd.length == 0)); + + cmd.codecId = RDPGFX_CODECID_PLANAR; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + free(cmd.data); + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + else + { + BOOL rc = 0; + const UINT32 w = cmd.right - cmd.left; + const UINT32 h = cmd.bottom - cmd.top; + const UINT32 length = w * 4 * h; + BYTE* data = malloc(length); + + WINPR_ASSERT(data); + + rc = freerdp_image_copy(data, PIXEL_FORMAT_BGRA32, 0, 0, 0, w, h, pSrcData, SrcFormat, + nSrcStep, cmd.left, cmd.top, NULL, 0); + WINPR_ASSERT(rc); + + cmd.data = data; + cmd.length = length; + cmd.codecId = RDPGFX_CODECID_UNCOMPRESSED; + + IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd, &cmdstart, + &cmdend); + free(data); + if (error) + { + WLog_ERR(TAG, "SurfaceFrameCommand failed with error %" PRIu32 "", error); + return FALSE; + } + } + return TRUE; +} + +/** + * Function description + * + * @return TRUE on success + */ +static BOOL shadow_client_send_surface_bits(rdpShadowClient* client, BYTE* pSrcData, + UINT32 nSrcStep, UINT16 nXSrc, UINT16 nYSrc, + UINT16 nWidth, UINT16 nHeight) +{ + BOOL ret = TRUE; + BOOL first = 0; + BOOL last = 0; + wStream* s = NULL; + size_t numMessages = 0; + UINT32 frameId = 0; + rdpUpdate* update = NULL; + rdpContext* context = (rdpContext*)client; + rdpSettings* settings = NULL; + rdpShadowEncoder* encoder = NULL; + SURFACE_BITS_COMMAND cmd = { 0 }; + UINT32 nsID = 0; + UINT32 rfxID = 0; + + if (!context || !pSrcData) + return FALSE; + + update = context->update; + settings = context->settings; + encoder = client->encoder; + + if (!update || !settings || !encoder) + return FALSE; + + if (encoder->frameAck) + frameId = shadow_encoder_create_frame_id(encoder); + + nsID = freerdp_settings_get_uint32(settings, FreeRDP_NSCodecId); + rfxID = freerdp_settings_get_uint32(settings, FreeRDP_RemoteFxCodecId); + if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) && (rfxID != 0)) + { + RFX_RECT rect = { 0 }; + + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_REMOTEFX) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_REMOTEFX"); + return FALSE; + } + + s = encoder->bs; + rect.x = nXSrc; + rect.y = nYSrc; + rect.width = nWidth; + rect.height = nHeight; + + const UINT32 MultifragMaxRequestSize = + freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize); + RFX_MESSAGE_LIST* messages = + rfx_encode_messages(encoder->rfx, &rect, 1, pSrcData, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + nSrcStep, &numMessages, MultifragMaxRequestSize); + if (!messages) + { + WLog_ERR(TAG, "rfx_encode_messages failed"); + return FALSE; + } + + cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; + WINPR_ASSERT(rfxID <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)rfxID; + cmd.destLeft = 0; + cmd.destTop = 0; + cmd.destRight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + cmd.destBottom = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + cmd.bmp.bpp = 32; + cmd.bmp.flags = 0; + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= UINT16_MAX); + WINPR_ASSERT(freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= UINT16_MAX); + cmd.bmp.width = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + cmd.bmp.height = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + cmd.skipCompression = TRUE; + + for (size_t i = 0; i < numMessages; i++) + { + Stream_SetPosition(s, 0); + + const RFX_MESSAGE* msg = rfx_message_list_get(messages, i); + if (!rfx_write_message(encoder->rfx, s, msg)) + { + rfx_message_list_free(messages); + WLog_ERR(TAG, "rfx_write_message failed"); + ret = FALSE; + break; + } + + WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX); + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + first = (i == 0) ? TRUE : FALSE; + last = ((i + 1) == numMessages) ? TRUE : FALSE; + + if (!encoder->frameAck) + IFCALLRET(update->SurfaceBits, ret, update->context, &cmd); + else + IFCALLRET(update->SurfaceFrameBits, ret, update->context, &cmd, first, last, + frameId); + + if (!ret) + { + WLog_ERR(TAG, "Send surface bits(RemoteFxCodec) failed"); + break; + } + } + + rfx_message_list_free(messages); + } + if (freerdp_settings_get_bool(settings, FreeRDP_NSCodec) && (nsID != 0)) + { + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_NSCODEC) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_NSCODEC"); + return FALSE; + } + + s = encoder->bs; + Stream_SetPosition(s, 0); + pSrcData = &pSrcData[(nYSrc * nSrcStep) + (nXSrc * 4)]; + nsc_compose_message(encoder->nsc, s, pSrcData, nWidth, nHeight, nSrcStep); + cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; + cmd.bmp.bpp = 32; + WINPR_ASSERT(nsID <= UINT16_MAX); + cmd.bmp.codecID = (UINT16)nsID; + cmd.destLeft = nXSrc; + cmd.destTop = nYSrc; + cmd.destRight = cmd.destLeft + nWidth; + cmd.destBottom = cmd.destTop + nHeight; + cmd.bmp.width = nWidth; + cmd.bmp.height = nHeight; + WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX); + cmd.bmp.bitmapDataLength = (UINT32)Stream_GetPosition(s); + cmd.bmp.bitmapData = Stream_Buffer(s); + first = TRUE; + last = TRUE; + + if (!encoder->frameAck) + IFCALLRET(update->SurfaceBits, ret, update->context, &cmd); + else + IFCALLRET(update->SurfaceFrameBits, ret, update->context, &cmd, first, last, frameId); + + if (!ret) + { + WLog_ERR(TAG, "Send surface bits(NSCodec) failed"); + } + } + + return ret; +} + +/** + * Function description + * + * @return TRUE on success + */ +static BOOL shadow_client_send_bitmap_update(rdpShadowClient* client, BYTE* pSrcData, + UINT32 nSrcStep, UINT16 nXSrc, UINT16 nYSrc, + UINT16 nWidth, UINT16 nHeight) +{ + BOOL ret = TRUE; + BYTE* data = NULL; + BYTE* buffer = NULL; + UINT32 k = 0; + UINT32 yIdx = 0; + UINT32 xIdx = 0; + UINT32 rows = 0; + UINT32 cols = 0; + UINT32 DstSize = 0; + UINT32 SrcFormat = 0; + BITMAP_DATA* bitmap = NULL; + rdpUpdate* update = NULL; + rdpContext* context = (rdpContext*)client; + rdpSettings* settings = NULL; + UINT32 totalBitmapSize = 0; + UINT32 updateSizeEstimate = 0; + BITMAP_DATA* bitmapData = NULL; + BITMAP_UPDATE bitmapUpdate; + rdpShadowEncoder* encoder = NULL; + + if (!context || !pSrcData) + return FALSE; + + update = context->update; + settings = context->settings; + encoder = client->encoder; + + if (!update || !settings || !encoder) + return FALSE; + + const UINT32 maxUpdateSize = + freerdp_settings_get_uint32(settings, FreeRDP_MultifragMaxRequestSize); + if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) < 32) + { + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_INTERLEAVED) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_INTERLEAVED"); + return FALSE; + } + } + else + { + if (shadow_encoder_prepare(encoder, FREERDP_CODEC_PLANAR) < 0) + { + WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_PLANAR"); + return FALSE; + } + } + + SrcFormat = PIXEL_FORMAT_BGRX32; + + if ((nXSrc % 4) != 0) + { + nWidth += (nXSrc % 4); + nXSrc -= (nXSrc % 4); + } + + if ((nYSrc % 4) != 0) + { + nHeight += (nYSrc % 4); + nYSrc -= (nYSrc % 4); + } + + rows = (nHeight / 64) + ((nHeight % 64) ? 1 : 0); + cols = (nWidth / 64) + ((nWidth % 64) ? 1 : 0); + k = 0; + totalBitmapSize = 0; + bitmapUpdate.number = rows * cols; + + if (!(bitmapData = (BITMAP_DATA*)calloc(bitmapUpdate.number, sizeof(BITMAP_DATA)))) + return FALSE; + + bitmapUpdate.rectangles = bitmapData; + + if ((nWidth % 4) != 0) + { + nWidth += (4 - (nWidth % 4)); + } + + if ((nHeight % 4) != 0) + { + nHeight += (4 - (nHeight % 4)); + } + + for (yIdx = 0; yIdx < rows; yIdx++) + { + for (xIdx = 0; xIdx < cols; xIdx++) + { + bitmap = &bitmapData[k]; + bitmap->width = 64; + bitmap->height = 64; + bitmap->destLeft = nXSrc + (xIdx * 64); + bitmap->destTop = nYSrc + (yIdx * 64); + + if ((INT64)(bitmap->destLeft + bitmap->width) > (nXSrc + nWidth)) + bitmap->width = (UINT32)(nXSrc + nWidth) - bitmap->destLeft; + + if ((INT64)(bitmap->destTop + bitmap->height) > (nYSrc + nHeight)) + bitmap->height = (UINT32)(nYSrc + nHeight) - bitmap->destTop; + + bitmap->destRight = bitmap->destLeft + bitmap->width - 1; + bitmap->destBottom = bitmap->destTop + bitmap->height - 1; + bitmap->compressed = TRUE; + + if ((bitmap->width < 4) || (bitmap->height < 4)) + continue; + + if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) < 32) + { + UINT32 bitsPerPixel = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth); + UINT32 bytesPerPixel = (bitsPerPixel + 7) / 8; + DstSize = 64 * 64 * 4; + buffer = encoder->grid[k]; + interleaved_compress(encoder->interleaved, buffer, &DstSize, bitmap->width, + bitmap->height, pSrcData, SrcFormat, nSrcStep, + bitmap->destLeft, bitmap->destTop, NULL, bitsPerPixel); + bitmap->bitmapDataStream = buffer; + bitmap->bitmapLength = DstSize; + bitmap->bitsPerPixel = bitsPerPixel; + bitmap->cbScanWidth = bitmap->width * bytesPerPixel; + bitmap->cbUncompressedSize = bitmap->width * bitmap->height * bytesPerPixel; + } + else + { + UINT32 dstSize = 0; + buffer = encoder->grid[k]; + data = &pSrcData[(bitmap->destTop * nSrcStep) + (bitmap->destLeft * 4)]; + + buffer = + freerdp_bitmap_compress_planar(encoder->planar, data, SrcFormat, bitmap->width, + bitmap->height, nSrcStep, buffer, &dstSize); + bitmap->bitmapDataStream = buffer; + bitmap->bitmapLength = dstSize; + bitmap->bitsPerPixel = 32; + bitmap->cbScanWidth = bitmap->width * 4; + bitmap->cbUncompressedSize = bitmap->width * bitmap->height * 4; + } + + bitmap->cbCompFirstRowSize = 0; + bitmap->cbCompMainBodySize = bitmap->bitmapLength; + totalBitmapSize += bitmap->bitmapLength; + k++; + } + } + + bitmapUpdate.number = k; + updateSizeEstimate = totalBitmapSize + (k * bitmapUpdate.number) + 16; + + if (updateSizeEstimate > maxUpdateSize) + { + UINT32 i = 0; + UINT32 j = 0; + UINT32 updateSize = 0; + UINT32 newUpdateSize = 0; + BITMAP_DATA* fragBitmapData = NULL; + + if (k > 0) + fragBitmapData = (BITMAP_DATA*)calloc(k, sizeof(BITMAP_DATA)); + + if (!fragBitmapData) + { + WLog_ERR(TAG, "Failed to allocate memory for fragBitmapData"); + ret = FALSE; + goto out; + } + + bitmapUpdate.rectangles = fragBitmapData; + i = j = 0; + updateSize = 1024; + + while (i < k) + { + newUpdateSize = updateSize + (bitmapData[i].bitmapLength + 16); + + if (newUpdateSize < maxUpdateSize) + { + CopyMemory(&fragBitmapData[j++], &bitmapData[i++], sizeof(BITMAP_DATA)); + updateSize = newUpdateSize; + } + + if ((newUpdateSize >= maxUpdateSize) || (i + 1) >= k) + { + bitmapUpdate.number = j; + IFCALLRET(update->BitmapUpdate, ret, context, &bitmapUpdate); + + if (!ret) + { + WLog_ERR(TAG, "BitmapUpdate failed"); + break; + } + + updateSize = 1024; + j = 0; + } + } + + free(fragBitmapData); + } + else + { + IFCALLRET(update->BitmapUpdate, ret, context, &bitmapUpdate); + + if (!ret) + { + WLog_ERR(TAG, "BitmapUpdate failed"); + } + } + +out: + free(bitmapData); + return ret; +} + +/** + * Function description + * + * @return TRUE on success (or nothing need to be updated) + */ +static BOOL shadow_client_send_surface_update(rdpShadowClient* client, SHADOW_GFX_STATUS* pStatus) +{ + BOOL ret = TRUE; + INT64 nXSrc = 0; + INT64 nYSrc = 0; + INT64 nWidth = 0; + INT64 nHeight = 0; + rdpContext* context = (rdpContext*)client; + rdpSettings* settings = NULL; + rdpShadowServer* server = NULL; + rdpShadowSurface* surface = NULL; + REGION16 invalidRegion; + RECTANGLE_16 surfaceRect; + const RECTANGLE_16* extents = NULL; + BYTE* pSrcData = NULL; + UINT32 nSrcStep = 0; + UINT32 SrcFormat = 0; + UINT32 numRects = 0; + const RECTANGLE_16* rects = NULL; + + if (!context || !pStatus) + return FALSE; + + settings = context->settings; + server = client->server; + + if (!settings || !server) + return FALSE; + + surface = client->inLobby ? server->lobby : server->surface; + + if (!surface) + return FALSE; + + EnterCriticalSection(&(client->lock)); + region16_init(&invalidRegion); + region16_copy(&invalidRegion, &(client->invalidRegion)); + region16_clear(&(client->invalidRegion)); + LeaveCriticalSection(&(client->lock)); + + EnterCriticalSection(&surface->lock); + rects = region16_rects(&(surface->invalidRegion), &numRects); + + for (UINT32 index = 0; index < numRects; index++) + region16_union_rect(&invalidRegion, &invalidRegion, &rects[index]); + + surfaceRect.left = 0; + surfaceRect.top = 0; + WINPR_ASSERT(surface->width <= UINT16_MAX); + WINPR_ASSERT(surface->height <= UINT16_MAX); + surfaceRect.right = (UINT16)surface->width; + surfaceRect.bottom = (UINT16)surface->height; + region16_intersect_rect(&invalidRegion, &invalidRegion, &surfaceRect); + + if (server->shareSubRect) + { + region16_intersect_rect(&invalidRegion, &invalidRegion, &(server->subRect)); + } + + if (region16_is_empty(&invalidRegion)) + { + /* No image region need to be updated. Success */ + goto out; + } + + extents = region16_extents(&invalidRegion); + nXSrc = extents->left; + nYSrc = extents->top; + nWidth = extents->right - extents->left; + nHeight = extents->bottom - extents->top; + pSrcData = surface->data; + nSrcStep = surface->scanline; + SrcFormat = surface->format; + + /* Move to new pSrcData / nXSrc / nYSrc according to sub rect */ + if (server->shareSubRect) + { + INT32 subX = 0; + INT32 subY = 0; + subX = server->subRect.left; + subY = server->subRect.top; + nXSrc -= subX; + nYSrc -= subY; + WINPR_ASSERT(nXSrc >= 0); + WINPR_ASSERT(nXSrc <= UINT16_MAX); + WINPR_ASSERT(nYSrc >= 0); + WINPR_ASSERT(nYSrc <= UINT16_MAX); + pSrcData = &pSrcData[((UINT16)subY * nSrcStep) + ((UINT16)subX * 4U)]; + } + + // WLog_INFO(TAG, "shadow_client_send_surface_update: x: %" PRId64 " y: %" PRId64 " width: %" + // PRId64 " height: %" PRId64 " right: %" PRId64 " bottom: %" PRId64, nXSrc, nYSrc, nWidth, + // nHeight, nXSrc + nWidth, nYSrc + nHeight); + + if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline)) + { + if (pStatus->gfxOpened && client->areGfxCapsReady) + { + /* GFX/h264 always full screen encoded */ + nWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + nHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + /* Create primary surface if have not */ + if (!pStatus->gfxSurfaceCreated) + { + /* Only init surface when we have h264 supported */ + if (!(ret = shadow_client_rdpgfx_reset_graphic(client))) + goto out; + + if (!(ret = shadow_client_rdpgfx_new_surface(client))) + goto out; + + pStatus->gfxSurfaceCreated = TRUE; + } + + WINPR_ASSERT(nWidth >= 0); + WINPR_ASSERT(nWidth <= UINT16_MAX); + WINPR_ASSERT(nHeight >= 0); + WINPR_ASSERT(nHeight <= UINT16_MAX); + ret = shadow_client_send_surface_gfx(client, pSrcData, nSrcStep, SrcFormat, 0, 0, + (UINT16)nWidth, (UINT16)nHeight); + } + else + { + ret = TRUE; + } + } + else if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) || + freerdp_settings_get_bool(settings, FreeRDP_NSCodec)) + { + WINPR_ASSERT(nXSrc >= 0); + WINPR_ASSERT(nXSrc <= UINT16_MAX); + WINPR_ASSERT(nYSrc >= 0); + WINPR_ASSERT(nYSrc <= UINT16_MAX); + WINPR_ASSERT(nWidth >= 0); + WINPR_ASSERT(nWidth <= UINT16_MAX); + WINPR_ASSERT(nHeight >= 0); + WINPR_ASSERT(nHeight <= UINT16_MAX); + ret = shadow_client_send_surface_bits(client, pSrcData, nSrcStep, (UINT16)nXSrc, + (UINT16)nYSrc, (UINT16)nWidth, (UINT16)nHeight); + } + else + { + WINPR_ASSERT(nXSrc >= 0); + WINPR_ASSERT(nXSrc <= UINT16_MAX); + WINPR_ASSERT(nYSrc >= 0); + WINPR_ASSERT(nYSrc <= UINT16_MAX); + WINPR_ASSERT(nWidth >= 0); + WINPR_ASSERT(nWidth <= UINT16_MAX); + WINPR_ASSERT(nHeight >= 0); + WINPR_ASSERT(nHeight <= UINT16_MAX); + ret = shadow_client_send_bitmap_update(client, pSrcData, nSrcStep, (UINT16)nXSrc, + (UINT16)nYSrc, (UINT16)nWidth, (UINT16)nHeight); + } + +out: + LeaveCriticalSection(&surface->lock); + region16_uninit(&invalidRegion); + return ret; +} + +/** + * Function description + * Notify client for resize. The new desktop width/height + * should have already been updated in rdpSettings. + * + * @return TRUE on success + */ +static BOOL shadow_client_send_resize(rdpShadowClient* client, SHADOW_GFX_STATUS* pStatus) +{ + rdpContext* context = (rdpContext*)client; + rdpSettings* settings = NULL; + freerdp_peer* peer = NULL; + + if (!context || !pStatus) + return FALSE; + + peer = context->peer; + settings = context->settings; + + if (!peer || !settings) + return FALSE; + + /** + * Unset client activated flag to avoid sending update message during + * resize. DesktopResize will reactive the client and + * shadow_client_activate would be invoked later. + */ + client->activated = FALSE; + + /* Close Gfx surfaces */ + if (pStatus->gfxSurfaceCreated) + { + if (!shadow_client_rdpgfx_release_surface(client)) + return FALSE; + + pStatus->gfxSurfaceCreated = FALSE; + } + + /* Send Resize */ + if (!shadow_send_desktop_resize(client)) + return FALSE; + shadow_reset_desktop_resize(client); + + /* Clear my invalidRegion. shadow_client_activate refreshes fullscreen */ + EnterCriticalSection(&(client->lock)); + region16_clear(&(client->invalidRegion)); + LeaveCriticalSection(&(client->lock)); + return TRUE; +} + +/** + * Function description + * Mark invalid region for client + * + * @return TRUE on success + */ +static BOOL shadow_client_surface_update(rdpShadowClient* client, REGION16* region) +{ + UINT32 numRects = 0; + const RECTANGLE_16* rects = NULL; + rects = region16_rects(region, &numRects); + shadow_client_mark_invalid(client, numRects, rects); + return TRUE; +} + +/** + * Function description + * Only union invalid region from server surface + * + * @return TRUE on success + */ +static INLINE BOOL shadow_client_no_surface_update(rdpShadowClient* client, + SHADOW_GFX_STATUS* pStatus) +{ + rdpShadowServer* server = NULL; + rdpShadowSurface* surface = NULL; + WINPR_UNUSED(pStatus); + WINPR_ASSERT(client); + server = client->server; + WINPR_ASSERT(server); + surface = client->inLobby ? server->lobby : server->surface; + return shadow_client_surface_update(client, &(surface->invalidRegion)); +} + +static int shadow_client_subsystem_process_message(rdpShadowClient* client, wMessage* message) +{ + rdpContext* context = (rdpContext*)client; + rdpUpdate* update = NULL; + + WINPR_ASSERT(message); + WINPR_ASSERT(context); + update = context->update; + WINPR_ASSERT(update); + + /* FIXME: the pointer updates appear to be broken when used with bulk compression and mstsc */ + + switch (message->id) + { + case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID: + { + POINTER_POSITION_UPDATE pointerPosition; + const SHADOW_MSG_OUT_POINTER_POSITION_UPDATE* msg = + (const SHADOW_MSG_OUT_POINTER_POSITION_UPDATE*)message->wParam; + pointerPosition.xPos = msg->xPos; + pointerPosition.yPos = msg->yPos; + + WINPR_ASSERT(client->server); + if (client->server->shareSubRect) + { + pointerPosition.xPos -= client->server->subRect.left; + pointerPosition.yPos -= client->server->subRect.top; + } + + if (client->activated) + { + if ((msg->xPos != client->pointerX) || (msg->yPos != client->pointerY)) + { + WINPR_ASSERT(update->pointer); + IFCALL(update->pointer->PointerPosition, context, &pointerPosition); + client->pointerX = msg->xPos; + client->pointerY = msg->yPos; + } + } + + break; + } + + case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID: + { + POINTER_NEW_UPDATE pointerNew = { 0 }; + POINTER_COLOR_UPDATE* pointerColor = { 0 }; + POINTER_CACHED_UPDATE pointerCached = { 0 }; + const SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* msg = + (const SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE*)message->wParam; + + WINPR_ASSERT(msg); + pointerNew.xorBpp = 24; + pointerColor = &(pointerNew.colorPtrAttr); + pointerColor->cacheIndex = 0; + pointerColor->hotSpotX = msg->xHot; + pointerColor->hotSpotY = msg->yHot; + pointerColor->width = msg->width; + pointerColor->height = msg->height; + pointerColor->lengthAndMask = msg->lengthAndMask; + pointerColor->lengthXorMask = msg->lengthXorMask; + pointerColor->xorMaskData = msg->xorMaskData; + pointerColor->andMaskData = msg->andMaskData; + pointerCached.cacheIndex = pointerColor->cacheIndex; + + if (client->activated) + { + IFCALL(update->pointer->PointerNew, context, &pointerNew); + IFCALL(update->pointer->PointerCached, context, &pointerCached); + } + + break; + } + + case SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID: + { + const SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES* msg = + (const SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES*)message->wParam; + + WINPR_ASSERT(msg); + + if (client->activated && client->rdpsnd && client->rdpsnd->Activated) + { + client->rdpsnd->src_format = msg->audio_format; + IFCALL(client->rdpsnd->SendSamples, client->rdpsnd, msg->buf, msg->nFrames, + msg->wTimestamp); + } + + break; + } + + case SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID: + { + const SHADOW_MSG_OUT_AUDIO_OUT_VOLUME* msg = + (const SHADOW_MSG_OUT_AUDIO_OUT_VOLUME*)message->wParam; + + if (client->activated && client->rdpsnd && client->rdpsnd->Activated) + { + IFCALL(client->rdpsnd->SetVolume, client->rdpsnd, msg->left, msg->right); + } + + break; + } + + default: + WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id); + break; + } + + shadow_client_free_queued_message(message); + return 1; +} + +static DWORD WINAPI shadow_client_thread(LPVOID arg) +{ + rdpShadowClient* client = (rdpShadowClient*)arg; + BOOL rc = FALSE; + DWORD status = 0; + wMessage message = { 0 }; + wMessage pointerPositionMsg = { 0 }; + wMessage pointerAlphaMsg = { 0 }; + wMessage audioVolumeMsg = { 0 }; + HANDLE ChannelEvent = 0; + void* UpdateSubscriber = NULL; + HANDLE UpdateEvent = 0; + freerdp_peer* peer = NULL; + rdpContext* context = NULL; + rdpSettings* settings = NULL; + rdpShadowServer* server = NULL; + rdpShadowSubsystem* subsystem = NULL; + wMessageQueue* MsgQueue = NULL; + /* This should only be visited in client thread */ + SHADOW_GFX_STATUS gfxstatus = { 0 }; + rdpUpdate* update = NULL; + + WINPR_ASSERT(client); + + MsgQueue = client->MsgQueue; + WINPR_ASSERT(MsgQueue); + + server = client->server; + WINPR_ASSERT(server); + subsystem = server->subsystem; + context = (rdpContext*)client; + peer = context->peer; + WINPR_ASSERT(peer); + WINPR_ASSERT(peer->context); + + settings = peer->context->settings; + WINPR_ASSERT(settings); + + peer->Capabilities = shadow_client_capabilities; + peer->PostConnect = shadow_client_post_connect; + peer->Activate = shadow_client_activate; + peer->Logon = shadow_client_logon; + shadow_input_register_callbacks(peer->context->input); + + rc = peer->Initialize(peer); + if (!rc) + goto out; + + update = peer->context->update; + WINPR_ASSERT(update); + + update->RefreshRect = shadow_client_refresh_rect; + update->SuppressOutput = shadow_client_suppress_output; + update->SurfaceFrameAcknowledge = shadow_client_surface_frame_acknowledge; + + if ((!client->vcm) || (!subsystem->updateEvent)) + goto out; + + UpdateSubscriber = shadow_multiclient_get_subscriber(subsystem->updateEvent); + + if (!UpdateSubscriber) + goto out; + + UpdateEvent = shadow_multiclient_getevent(UpdateSubscriber); + WINPR_ASSERT(UpdateEvent); + + ChannelEvent = WTSVirtualChannelManagerGetEventHandle(client->vcm); + WINPR_ASSERT(ChannelEvent); + + rc = freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE); + WINPR_ASSERT(rc); + rc = freerdp_settings_set_bool(settings, FreeRDP_HasHorizontalWheel, TRUE); + WINPR_ASSERT(rc); + rc = freerdp_settings_set_bool(settings, FreeRDP_HasExtendedMouseEvent, TRUE); + WINPR_ASSERT(rc); + rc = freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE); + WINPR_ASSERT(rc); + while (1) + { + HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD nCount = 0; + events[nCount++] = UpdateEvent; + { + DWORD tmp = peer->GetEventHandles(peer, &events[nCount], 64 - nCount); + + if (tmp == 0) + { + WLog_ERR(TAG, "Failed to get FreeRDP transport event handles"); + goto fail; + } + + nCount += tmp; + } + events[nCount++] = ChannelEvent; + events[nCount++] = MessageQueue_Event(MsgQueue); + + HANDLE gfxevent = rdpgfx_server_get_event_handle(client->rdpgfx); + + if (gfxevent) + events[nCount++] = gfxevent; + + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + goto fail; + + if (WaitForSingleObject(UpdateEvent, 0) == WAIT_OBJECT_0) + { + /* The UpdateEvent means to start sending current frame. It is + * triggered from subsystem implementation and it should ensure + * that the screen and primary surface meta data (width, height, + * scanline, invalid region, etc) is not changed until it is reset + * (at shadow_multiclient_consume). As best practice, subsystem + * implementation should invoke shadow_subsystem_frame_update which + * triggers the event and then wait for completion */ + if (client->activated && !client->suppressOutput) + { + /* Send screen update or resize to this client */ + + /* Check resize */ + if (shadow_client_recalc_desktop_size(client)) + { + /* Screen size changed, do resize */ + if (!shadow_client_send_resize(client, &gfxstatus)) + { + WLog_ERR(TAG, "Failed to send resize message"); + break; + } + } + else + { + /* Send frame */ + if (!shadow_client_send_surface_update(client, &gfxstatus)) + { + WLog_ERR(TAG, "Failed to send surface update"); + break; + } + } + } + else + { + /* Our client don't receive graphic updates. Just save the invalid region */ + if (!shadow_client_no_surface_update(client, &gfxstatus)) + { + WLog_ERR(TAG, "Failed to handle surface update"); + break; + } + } + + /* + * The return value of shadow_multiclient_consume is whether or not + * the subscriber really consumes the event. It's not cared currently. + */ + (void)shadow_multiclient_consume(UpdateSubscriber); + } + + WINPR_ASSERT(peer->CheckFileDescriptor); + if (!peer->CheckFileDescriptor(peer)) + { + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + goto fail; + } + + if (client->activated && + WTSVirtualChannelManagerIsChannelJoined(client->vcm, DRDYNVC_SVC_CHANNEL_NAME)) + { + switch (WTSVirtualChannelManagerGetDrdynvcState(client->vcm)) + { + /* Dynamic channel status may have been changed after processing */ + case DRDYNVC_STATE_NONE: + + /* Call this routine to Initialize drdynvc channel */ + if (!WTSVirtualChannelManagerCheckFileDescriptor(client->vcm)) + { + WLog_ERR(TAG, "Failed to initialize drdynvc channel"); + goto fail; + } + + break; + + case DRDYNVC_STATE_READY: +#if defined(CHANNEL_AUDIN_SERVER) + if (client->audin && !IFCALLRESULT(TRUE, client->audin->IsOpen, client->audin)) + { + if (!IFCALLRESULT(FALSE, client->audin->Open, client->audin)) + { + WLog_ERR(TAG, "Failed to initialize audin channel"); + goto fail; + } + } +#endif + + /* Init RDPGFX dynamic channel */ + if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline) && + client->rdpgfx && !gfxstatus.gfxOpened) + { + client->rdpgfx->FrameAcknowledge = shadow_client_rdpgfx_frame_acknowledge; + client->rdpgfx->CapsAdvertise = shadow_client_rdpgfx_caps_advertise; + + if (!client->rdpgfx->Open(client->rdpgfx)) + { + WLog_WARN(TAG, "Failed to open GraphicsPipeline"); + if (!freerdp_settings_set_bool(settings, + FreeRDP_SupportGraphicsPipeline, FALSE)) + goto fail; + } + else + { + gfxstatus.gfxOpened = TRUE; + WLog_INFO(TAG, "Gfx Pipeline Opened"); + } + } + + break; + + default: + break; + } + } + + if (WaitForSingleObject(ChannelEvent, 0) == WAIT_OBJECT_0) + { + if (!WTSVirtualChannelManagerCheckFileDescriptor(client->vcm)) + { + WLog_ERR(TAG, "WTSVirtualChannelManagerCheckFileDescriptor failure"); + goto fail; + } + } + + if (gfxevent) + { + if (WaitForSingleObject(gfxevent, 0) == WAIT_OBJECT_0) + { + rdpgfx_server_handle_messages(client->rdpgfx); + } + } + + if (WaitForSingleObject(MessageQueue_Event(MsgQueue), 0) == WAIT_OBJECT_0) + { + /* Drain messages. Pointer update could be accumulated. */ + pointerPositionMsg.id = 0; + pointerPositionMsg.Free = NULL; + pointerAlphaMsg.id = 0; + pointerAlphaMsg.Free = NULL; + audioVolumeMsg.id = 0; + audioVolumeMsg.Free = NULL; + + while (MessageQueue_Peek(MsgQueue, &message, TRUE)) + { + if (message.id == WMQ_QUIT) + { + break; + } + + switch (message.id) + { + case SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID: + /* Abandon previous message */ + shadow_client_free_queued_message(&pointerPositionMsg); + pointerPositionMsg = message; + break; + + case SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID: + /* Abandon previous message */ + shadow_client_free_queued_message(&pointerAlphaMsg); + pointerAlphaMsg = message; + break; + + case SHADOW_MSG_OUT_AUDIO_OUT_VOLUME_ID: + /* Abandon previous message */ + shadow_client_free_queued_message(&audioVolumeMsg); + audioVolumeMsg = message; + break; + + default: + shadow_client_subsystem_process_message(client, &message); + break; + } + } + + if (message.id == WMQ_QUIT) + { + /* Release stored message */ + shadow_client_free_queued_message(&pointerPositionMsg); + shadow_client_free_queued_message(&pointerAlphaMsg); + shadow_client_free_queued_message(&audioVolumeMsg); + goto fail; + } + else + { + /* Process accumulated messages if needed */ + if (pointerPositionMsg.id) + { + shadow_client_subsystem_process_message(client, &pointerPositionMsg); + } + + if (pointerAlphaMsg.id) + { + shadow_client_subsystem_process_message(client, &pointerAlphaMsg); + } + + if (audioVolumeMsg.id) + { + shadow_client_subsystem_process_message(client, &audioVolumeMsg); + } + } + } + } + +fail: + + /* Free channels early because we establish channels in post connect */ +#if defined(CHANNEL_AUDIN_SERVER) + if (client->audin && !IFCALLRESULT(TRUE, client->audin->IsOpen, client->audin)) + { + if (!IFCALLRESULT(FALSE, client->audin->Close, client->audin)) + { + WLog_WARN(TAG, "AUDIN shutdown failure!"); + } + } +#endif + + if (gfxstatus.gfxOpened) + { + if (gfxstatus.gfxSurfaceCreated) + { + if (!shadow_client_rdpgfx_release_surface(client)) + WLog_WARN(TAG, "GFX release surface failure!"); + } + + WINPR_ASSERT(client->rdpgfx); + WINPR_ASSERT(client->rdpgfx->Close); + rc = client->rdpgfx->Close(client->rdpgfx); + WINPR_ASSERT(rc); + } + + shadow_client_channels_free(client); + + if (UpdateSubscriber) + { + shadow_multiclient_release_subscriber(UpdateSubscriber); + UpdateSubscriber = NULL; + } + + if (peer->connected && subsystem->ClientDisconnect) + { + subsystem->ClientDisconnect(subsystem, client); + } + +out: + WINPR_ASSERT(peer->Disconnect); + peer->Disconnect(peer); + freerdp_peer_context_free(peer); + freerdp_peer_free(peer); + ExitThread(0); + return 0; +} + +BOOL shadow_client_accepted(freerdp_listener* listener, freerdp_peer* peer) +{ + rdpShadowClient* client = NULL; + rdpShadowServer* server = NULL; + + if (!listener || !peer) + return FALSE; + + server = (rdpShadowServer*)listener->info; + WINPR_ASSERT(server); + + peer->ContextExtra = (void*)server; + peer->ContextSize = sizeof(rdpShadowClient); + peer->ContextNew = shadow_client_context_new; + peer->ContextFree = shadow_client_context_free; + + if (!freerdp_peer_context_new_ex(peer, server->settings)) + return FALSE; + + client = (rdpShadowClient*)peer->context; + WINPR_ASSERT(client); + + if (!(client->thread = CreateThread(NULL, 0, shadow_client_thread, client, 0, NULL))) + { + freerdp_peer_context_free(peer); + return FALSE; + } + else + { + /* Close the thread handle to make it detached. */ + CloseHandle(client->thread); + client->thread = NULL; + } + + return TRUE; +} + +static void shadow_msg_out_addref(wMessage* message) +{ + SHADOW_MSG_OUT* msg = NULL; + + WINPR_ASSERT(message); + msg = (SHADOW_MSG_OUT*)message->wParam; + WINPR_ASSERT(msg); + + InterlockedIncrement(&(msg->refCount)); +} + +static void shadow_msg_out_release(wMessage* message) +{ + SHADOW_MSG_OUT* msg = NULL; + + WINPR_ASSERT(message); + msg = (SHADOW_MSG_OUT*)message->wParam; + WINPR_ASSERT(msg); + + if (InterlockedDecrement(&(msg->refCount)) <= 0) + { + IFCALL(msg->Free, message->id, msg); + } +} + +static BOOL shadow_client_dispatch_msg(rdpShadowClient* client, wMessage* message) +{ + if (!client || !message) + return FALSE; + + /* Add reference when it is posted */ + shadow_msg_out_addref(message); + + WINPR_ASSERT(client->MsgQueue); + if (MessageQueue_Dispatch(client->MsgQueue, message)) + return TRUE; + else + { + /* Release the reference since post failed */ + shadow_msg_out_release(message); + return FALSE; + } +} + +BOOL shadow_client_post_msg(rdpShadowClient* client, void* context, UINT32 type, + SHADOW_MSG_OUT* msg, void* lParam) +{ + wMessage message = { 0 }; + message.context = context; + message.id = type; + message.wParam = (void*)msg; + message.lParam = lParam; + message.Free = shadow_msg_out_release; + return shadow_client_dispatch_msg(client, &message); +} + +int shadow_client_boardcast_msg(rdpShadowServer* server, void* context, UINT32 type, + SHADOW_MSG_OUT* msg, void* lParam) +{ + wMessage message = { 0 }; + rdpShadowClient* client = NULL; + int count = 0; + + WINPR_ASSERT(server); + WINPR_ASSERT(msg); + + message.context = context; + message.id = type; + message.wParam = (void*)msg; + message.lParam = lParam; + message.Free = shadow_msg_out_release; + /* First add reference as we reference it in this function. + * Therefore it would not be free'ed during post. */ + shadow_msg_out_addref(&message); + + WINPR_ASSERT(server->clients); + ArrayList_Lock(server->clients); + + for (size_t index = 0; index < ArrayList_Count(server->clients); index++) + { + client = (rdpShadowClient*)ArrayList_GetItem(server->clients, index); + + if (shadow_client_dispatch_msg(client, &message)) + { + count++; + } + } + + ArrayList_Unlock(server->clients); + /* Release the reference for this function */ + shadow_msg_out_release(&message); + return count; +} + +int shadow_client_boardcast_quit(rdpShadowServer* server, int nExitCode) +{ + wMessageQueue* queue = NULL; + int count = 0; + + WINPR_ASSERT(server); + WINPR_ASSERT(server->clients); + + ArrayList_Lock(server->clients); + + for (size_t index = 0; index < ArrayList_Count(server->clients); index++) + { + queue = ((rdpShadowClient*)ArrayList_GetItem(server->clients, index))->MsgQueue; + + if (MessageQueue_PostQuit(queue, nExitCode)) + { + count++; + } + } + + ArrayList_Unlock(server->clients); + return count; +} diff --git a/server/shadow/shadow_client.h b/server/shadow/shadow_client.h new file mode 100644 index 0000000..9334924 --- /dev/null +++ b/server/shadow/shadow_client.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_CLIENT_H +#define FREERDP_SERVER_SHADOW_CLIENT_H + +#include <freerdp/server/shadow.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + BOOL shadow_client_accepted(freerdp_listener* instance, freerdp_peer* client); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_CLIENT_H */ diff --git a/server/shadow/shadow_encoder.c b/server/shadow/shadow_encoder.c new file mode 100644 index 0000000..fc1747c --- /dev/null +++ b/server/shadow/shadow_encoder.c @@ -0,0 +1,520 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/assert.h> + +#include "shadow.h" + +#include "shadow_encoder.h" + +#include <freerdp/log.h> +#define TAG CLIENT_TAG("shadow") + +UINT32 shadow_encoder_preferred_fps(rdpShadowEncoder* encoder) +{ + /* Return preferred fps calculated according to the last + * sent frame id and last client-acknowledged frame id. + */ + return encoder->fps; +} + +UINT32 shadow_encoder_inflight_frames(rdpShadowEncoder* encoder) +{ + /* Return inflight frame count. + * If queueDepth is SUSPEND_FRAME_ACKNOWLEDGEMENT, count = 0 + * Otherwise, calculate count = + * <last sent frame id> - <last client-acknowledged frame id> + * Note: This function is exported so that subsystem could + * implement its own strategy to tune fps. + */ + return (encoder->queueDepth == SUSPEND_FRAME_ACKNOWLEDGEMENT) + ? 0 + : encoder->frameId - encoder->lastAckframeId; +} + +UINT32 shadow_encoder_create_frame_id(rdpShadowEncoder* encoder) +{ + UINT32 frameId = 0; + UINT32 inFlightFrames = shadow_encoder_inflight_frames(encoder); + + /* + * Calculate preferred fps according to how much frames are + * in-progress. Note that it only works when subsytem implementation + * calls shadow_encoder_preferred_fps and takes the suggestion. + */ + if (inFlightFrames > 1) + { + encoder->fps = (100 / (inFlightFrames + 1) * encoder->maxFps) / 100; + } + else + { + encoder->fps += 2; + + if (encoder->fps > encoder->maxFps) + encoder->fps = encoder->maxFps; + } + + if (encoder->fps < 1) + encoder->fps = 1; + + frameId = ++encoder->frameId; + return frameId; +} + +static int shadow_encoder_init_grid(rdpShadowEncoder* encoder) +{ + UINT32 tileSize = 0; + UINT32 tileCount = 0; + encoder->gridWidth = ((encoder->width + (encoder->maxTileWidth - 1)) / encoder->maxTileWidth); + encoder->gridHeight = + ((encoder->height + (encoder->maxTileHeight - 1)) / encoder->maxTileHeight); + tileSize = encoder->maxTileWidth * encoder->maxTileHeight * 4; + tileCount = encoder->gridWidth * encoder->gridHeight; + encoder->gridBuffer = (BYTE*)calloc(tileSize, tileCount); + + if (!encoder->gridBuffer) + return -1; + + encoder->grid = (BYTE**)calloc(tileCount, sizeof(BYTE*)); + + if (!encoder->grid) + return -1; + + for (UINT32 i = 0; i < encoder->gridHeight; i++) + { + for (UINT32 j = 0; j < encoder->gridWidth; j++) + { + UINT32 k = (i * encoder->gridWidth) + j; + encoder->grid[k] = &(encoder->gridBuffer[k * tileSize]); + } + } + + return 0; +} + +static int shadow_encoder_uninit_grid(rdpShadowEncoder* encoder) +{ + if (encoder->gridBuffer) + { + free(encoder->gridBuffer); + encoder->gridBuffer = NULL; + } + + if (encoder->grid) + { + free(encoder->grid); + encoder->grid = NULL; + } + + encoder->gridWidth = 0; + encoder->gridHeight = 0; + return 0; +} + +static int shadow_encoder_init_rfx(rdpShadowEncoder* encoder) +{ + if (!encoder->rfx) + encoder->rfx = rfx_context_new_ex( + TRUE, freerdp_settings_get_uint32(encoder->server->settings, FreeRDP_ThreadingFlags)); + + if (!encoder->rfx) + goto fail; + + if (!rfx_context_reset(encoder->rfx, encoder->width, encoder->height)) + goto fail; + + rfx_context_set_mode(encoder->rfx, encoder->server->rfxMode); + rfx_context_set_pixel_format(encoder->rfx, PIXEL_FORMAT_BGRX32); + encoder->codecs |= FREERDP_CODEC_REMOTEFX; + return 1; +fail: + rfx_context_free(encoder->rfx); + return -1; +} + +static int shadow_encoder_init_nsc(rdpShadowEncoder* encoder) +{ + rdpContext* context = (rdpContext*)encoder->client; + rdpSettings* settings = context->settings; + + if (!encoder->nsc) + encoder->nsc = nsc_context_new(); + + if (!encoder->nsc) + goto fail; + + if (!nsc_context_reset(encoder->nsc, encoder->width, encoder->height)) + goto fail; + + if (!nsc_context_set_parameters( + encoder->nsc, NSC_COLOR_LOSS_LEVEL, + freerdp_settings_get_uint32(settings, FreeRDP_NSCodecColorLossLevel))) + goto fail; + if (!nsc_context_set_parameters( + encoder->nsc, NSC_ALLOW_SUBSAMPLING, + freerdp_settings_get_bool(settings, FreeRDP_NSCodecAllowSubsampling))) + goto fail; + if (!nsc_context_set_parameters( + encoder->nsc, NSC_DYNAMIC_COLOR_FIDELITY, + !freerdp_settings_get_bool(settings, FreeRDP_NSCodecAllowDynamicColorFidelity))) + goto fail; + if (!nsc_context_set_parameters(encoder->nsc, NSC_COLOR_FORMAT, PIXEL_FORMAT_BGRX32)) + goto fail; + encoder->codecs |= FREERDP_CODEC_NSCODEC; + return 1; +fail: + nsc_context_free(encoder->nsc); + return -1; +} + +static int shadow_encoder_init_planar(rdpShadowEncoder* encoder) +{ + DWORD planarFlags = 0; + rdpContext* context = (rdpContext*)encoder->client; + rdpSettings* settings = context->settings; + + if (freerdp_settings_get_bool(settings, FreeRDP_DrawAllowSkipAlpha)) + planarFlags |= PLANAR_FORMAT_HEADER_NA; + + planarFlags |= PLANAR_FORMAT_HEADER_RLE; + + if (!encoder->planar) + { + encoder->planar = freerdp_bitmap_planar_context_new(planarFlags, encoder->maxTileWidth, + encoder->maxTileHeight); + } + + if (!encoder->planar) + goto fail; + + if (!freerdp_bitmap_planar_context_reset(encoder->planar, encoder->maxTileWidth, + encoder->maxTileHeight)) + goto fail; + + encoder->codecs |= FREERDP_CODEC_PLANAR; + return 1; +fail: + freerdp_bitmap_planar_context_free(encoder->planar); + return -1; +} + +static int shadow_encoder_init_interleaved(rdpShadowEncoder* encoder) +{ + if (!encoder->interleaved) + encoder->interleaved = bitmap_interleaved_context_new(TRUE); + + if (!encoder->interleaved) + goto fail; + + if (!bitmap_interleaved_context_reset(encoder->interleaved)) + goto fail; + + encoder->codecs |= FREERDP_CODEC_INTERLEAVED; + return 1; +fail: + bitmap_interleaved_context_free(encoder->interleaved); + return -1; +} + +static int shadow_encoder_init_h264(rdpShadowEncoder* encoder) +{ + if (!encoder->h264) + encoder->h264 = h264_context_new(TRUE); + + if (!encoder->h264) + goto fail; + + if (!h264_context_reset(encoder->h264, encoder->width, encoder->height)) + goto fail; + + if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_RATECONTROL, + encoder->server->h264RateControlMode)) + goto fail; + if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_BITRATE, + encoder->server->h264BitRate)) + goto fail; + if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_FRAMERATE, + encoder->server->h264FrameRate)) + goto fail; + if (!h264_context_set_option(encoder->h264, H264_CONTEXT_OPTION_QP, encoder->server->h264QP)) + goto fail; + + encoder->codecs |= FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444; + return 1; +fail: + h264_context_free(encoder->h264); + return -1; +} + +static int shadow_encoder_init_progressive(rdpShadowEncoder* encoder) +{ + WINPR_ASSERT(encoder); + if (!encoder->progressive) + encoder->progressive = progressive_context_new(TRUE); + + if (!encoder->progressive) + goto fail; + + if (!progressive_context_reset(encoder->progressive)) + goto fail; + + encoder->codecs |= FREERDP_CODEC_PROGRESSIVE; + return 1; +fail: + progressive_context_free(encoder->progressive); + return -1; +} + +static int shadow_encoder_init(rdpShadowEncoder* encoder) +{ + encoder->width = encoder->server->screen->width; + encoder->height = encoder->server->screen->height; + encoder->maxTileWidth = 64; + encoder->maxTileHeight = 64; + shadow_encoder_init_grid(encoder); + + if (!encoder->bs) + encoder->bs = Stream_New(NULL, encoder->maxTileWidth * encoder->maxTileHeight * 4ULL); + + if (!encoder->bs) + return -1; + + return 1; +} + +static int shadow_encoder_uninit_rfx(rdpShadowEncoder* encoder) +{ + if (encoder->rfx) + { + rfx_context_free(encoder->rfx); + encoder->rfx = NULL; + } + + encoder->codecs &= (UINT32)~FREERDP_CODEC_REMOTEFX; + return 1; +} + +static int shadow_encoder_uninit_nsc(rdpShadowEncoder* encoder) +{ + if (encoder->nsc) + { + nsc_context_free(encoder->nsc); + encoder->nsc = NULL; + } + + encoder->codecs &= (UINT32)~FREERDP_CODEC_NSCODEC; + return 1; +} + +static int shadow_encoder_uninit_planar(rdpShadowEncoder* encoder) +{ + if (encoder->planar) + { + freerdp_bitmap_planar_context_free(encoder->planar); + encoder->planar = NULL; + } + + encoder->codecs &= (UINT32)~FREERDP_CODEC_PLANAR; + return 1; +} + +static int shadow_encoder_uninit_interleaved(rdpShadowEncoder* encoder) +{ + if (encoder->interleaved) + { + bitmap_interleaved_context_free(encoder->interleaved); + encoder->interleaved = NULL; + } + + encoder->codecs &= (UINT32)~FREERDP_CODEC_INTERLEAVED; + return 1; +} + +static int shadow_encoder_uninit_h264(rdpShadowEncoder* encoder) +{ + if (encoder->h264) + { + h264_context_free(encoder->h264); + encoder->h264 = NULL; + } + + encoder->codecs &= (UINT32) ~(FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444); + return 1; +} + +static int shadow_encoder_uninit_progressive(rdpShadowEncoder* encoder) +{ + WINPR_ASSERT(encoder); + if (encoder->progressive) + { + progressive_context_free(encoder->progressive); + encoder->progressive = NULL; + } + + encoder->codecs &= (UINT32)~FREERDP_CODEC_PROGRESSIVE; + return 1; +} + +static int shadow_encoder_uninit(rdpShadowEncoder* encoder) +{ + shadow_encoder_uninit_grid(encoder); + + if (encoder->bs) + { + Stream_Free(encoder->bs, TRUE); + encoder->bs = NULL; + } + + shadow_encoder_uninit_rfx(encoder); + + shadow_encoder_uninit_nsc(encoder); + + shadow_encoder_uninit_planar(encoder); + + shadow_encoder_uninit_interleaved(encoder); + shadow_encoder_uninit_h264(encoder); + + shadow_encoder_uninit_progressive(encoder); + + return 1; +} + +int shadow_encoder_reset(rdpShadowEncoder* encoder) +{ + int status = 0; + UINT32 codecs = encoder->codecs; + rdpContext* context = (rdpContext*)encoder->client; + rdpSettings* settings = context->settings; + status = shadow_encoder_uninit(encoder); + + if (status < 0) + return -1; + + status = shadow_encoder_init(encoder); + + if (status < 0) + return -1; + + status = shadow_encoder_prepare(encoder, codecs); + + if (status < 0) + return -1; + + encoder->fps = 16; + encoder->maxFps = 32; + encoder->frameId = 0; + encoder->lastAckframeId = 0; + encoder->frameAck = freerdp_settings_get_bool(settings, FreeRDP_SurfaceFrameMarkerEnabled); + return 1; +} + +int shadow_encoder_prepare(rdpShadowEncoder* encoder, UINT32 codecs) +{ + int status = 0; + + if ((codecs & FREERDP_CODEC_REMOTEFX) && !(encoder->codecs & FREERDP_CODEC_REMOTEFX)) + { + WLog_DBG(TAG, "initializing RemoteFX encoder"); + status = shadow_encoder_init_rfx(encoder); + + if (status < 0) + return -1; + } + + if ((codecs & FREERDP_CODEC_NSCODEC) && !(encoder->codecs & FREERDP_CODEC_NSCODEC)) + { + WLog_DBG(TAG, "initializing NSCodec encoder"); + status = shadow_encoder_init_nsc(encoder); + + if (status < 0) + return -1; + } + + if ((codecs & FREERDP_CODEC_PLANAR) && !(encoder->codecs & FREERDP_CODEC_PLANAR)) + { + WLog_DBG(TAG, "initializing planar bitmap encoder"); + status = shadow_encoder_init_planar(encoder); + + if (status < 0) + return -1; + } + + if ((codecs & FREERDP_CODEC_INTERLEAVED) && !(encoder->codecs & FREERDP_CODEC_INTERLEAVED)) + { + WLog_DBG(TAG, "initializing interleaved bitmap encoder"); + status = shadow_encoder_init_interleaved(encoder); + + if (status < 0) + return -1; + } + + if ((codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444)) && + !(encoder->codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444))) + { + WLog_DBG(TAG, "initializing H.264 encoder"); + status = shadow_encoder_init_h264(encoder); + + if (status < 0) + return -1; + } + + if ((codecs & FREERDP_CODEC_PROGRESSIVE) && !(encoder->codecs & FREERDP_CODEC_PROGRESSIVE)) + { + WLog_DBG(TAG, "initializing progressive encoder"); + status = shadow_encoder_init_progressive(encoder); + + if (status < 0) + return -1; + } + + return 1; +} + +rdpShadowEncoder* shadow_encoder_new(rdpShadowClient* client) +{ + rdpShadowEncoder* encoder = NULL; + rdpShadowServer* server = client->server; + encoder = (rdpShadowEncoder*)calloc(1, sizeof(rdpShadowEncoder)); + + if (!encoder) + return NULL; + + encoder->client = client; + encoder->server = server; + encoder->fps = 16; + encoder->maxFps = 32; + + if (shadow_encoder_init(encoder) < 0) + { + free(encoder); + return NULL; + } + + return encoder; +} + +void shadow_encoder_free(rdpShadowEncoder* encoder) +{ + if (!encoder) + return; + + shadow_encoder_uninit(encoder); + free(encoder); +} diff --git a/server/shadow/shadow_encoder.h b/server/shadow/shadow_encoder.h new file mode 100644 index 0000000..dfe00f3 --- /dev/null +++ b/server/shadow/shadow_encoder.h @@ -0,0 +1,81 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_ENCODER_H +#define FREERDP_SERVER_SHADOW_ENCODER_H + +#include <winpr/crt.h> +#include <winpr/stream.h> + +#include <freerdp/freerdp.h> +#include <freerdp/codecs.h> + +#include <freerdp/server/shadow.h> + +struct rdp_shadow_encoder +{ + rdpShadowClient* client; + rdpShadowServer* server; + + UINT32 width; + UINT32 height; + UINT32 codecs; + + BYTE** grid; + UINT32 gridWidth; + UINT32 gridHeight; + BYTE* gridBuffer; + UINT32 maxTileWidth; + UINT32 maxTileHeight; + + wStream* bs; + + RFX_CONTEXT* rfx; + NSC_CONTEXT* nsc; + BITMAP_PLANAR_CONTEXT* planar; + BITMAP_INTERLEAVED_CONTEXT* interleaved; + H264_CONTEXT* h264; + PROGRESSIVE_CONTEXT* progressive; + + UINT32 fps; + UINT32 maxFps; + BOOL frameAck; + UINT32 frameId; + UINT32 lastAckframeId; + UINT32 queueDepth; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + + int shadow_encoder_reset(rdpShadowEncoder* encoder); + int shadow_encoder_prepare(rdpShadowEncoder* encoder, UINT32 codecs); + UINT32 shadow_encoder_create_frame_id(rdpShadowEncoder* encoder); + + void shadow_encoder_free(rdpShadowEncoder* encoder); + + WINPR_ATTR_MALLOC(shadow_encoder_free, 1) + rdpShadowEncoder* shadow_encoder_new(rdpShadowClient* client); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_ENCODER_H */ diff --git a/server/shadow/shadow_encomsp.c b/server/shadow/shadow_encomsp.c new file mode 100644 index 0000000..8a3a468 --- /dev/null +++ b/server/shadow/shadow_encomsp.c @@ -0,0 +1,129 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * 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 <freerdp/config.h> + +#include <freerdp/log.h> +#include "shadow.h" + +#include "shadow_encomsp.h" + +#define TAG SERVER_TAG("shadow") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +encomsp_change_participant_control_level(EncomspServerContext* context, + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* pdu) +{ + BOOL inLobby = 0; + BOOL mayView = 0; + BOOL mayInteract = 0; + rdpShadowClient* client = (rdpShadowClient*)context->custom; + + WLog_INFO(TAG, + "ChangeParticipantControlLevel: ParticipantId: %" PRIu32 " Flags: 0x%04" PRIX16 "", + pdu->ParticipantId, pdu->Flags); + + mayView = (pdu->Flags & ENCOMSP_MAY_VIEW) ? TRUE : FALSE; + mayInteract = (pdu->Flags & ENCOMSP_MAY_INTERACT) ? TRUE : FALSE; + + if (mayInteract && !mayView) + mayView = TRUE; /* may interact implies may view */ + + if (mayInteract) + { + if (!client->mayInteract) + { + /* request interact + view */ + client->mayInteract = TRUE; + client->mayView = TRUE; + } + } + else if (mayView) + { + if (client->mayInteract) + { + /* release interact */ + client->mayInteract = FALSE; + } + else if (!client->mayView) + { + /* request view */ + client->mayView = TRUE; + } + } + else + { + if (client->mayInteract) + { + /* release interact + view */ + client->mayView = FALSE; + client->mayInteract = FALSE; + } + else if (client->mayView) + { + /* release view */ + client->mayView = FALSE; + client->mayInteract = FALSE; + } + } + + inLobby = client->mayView ? FALSE : TRUE; + + if (inLobby != client->inLobby) + { + shadow_encoder_reset(client->encoder); + client->inLobby = inLobby; + } + + return CHANNEL_RC_OK; +} + +int shadow_client_encomsp_init(rdpShadowClient* client) +{ + EncomspServerContext* encomsp = NULL; + + encomsp = client->encomsp = encomsp_server_context_new(client->vcm); + + encomsp->rdpcontext = &client->context; + + encomsp->custom = (void*)client; + + encomsp->ChangeParticipantControlLevel = encomsp_change_participant_control_level; + + if (client->encomsp) + client->encomsp->Start(client->encomsp); + + return 1; +} + +void shadow_client_encomsp_uninit(rdpShadowClient* client) +{ + if (client->encomsp) + { + client->encomsp->Stop(client->encomsp); + encomsp_server_context_free(client->encomsp); + client->encomsp = NULL; + } +} diff --git a/server/shadow/shadow_encomsp.h b/server/shadow/shadow_encomsp.h new file mode 100644 index 0000000..6562e25 --- /dev/null +++ b/server/shadow/shadow_encomsp.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_ENCOMSP_H +#define FREERDP_SERVER_SHADOW_ENCOMSP_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + int shadow_client_encomsp_init(rdpShadowClient* client); + void shadow_client_encomsp_uninit(rdpShadowClient* client); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_ENCOMSP_H */ diff --git a/server/shadow/shadow_input.c b/server/shadow/shadow_input.c new file mode 100644 index 0000000..97268b8 --- /dev/null +++ b/server/shadow/shadow_input.c @@ -0,0 +1,114 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include "shadow.h" + +static BOOL shadow_input_synchronize_event(rdpInput* input, UINT32 flags) +{ + rdpShadowClient* client = (rdpShadowClient*)input->context; + rdpShadowSubsystem* subsystem = client->server->subsystem; + + if (!client->mayInteract) + return TRUE; + + return IFCALLRESULT(TRUE, subsystem->SynchronizeEvent, subsystem, client, flags); +} + +static BOOL shadow_input_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code) +{ + rdpShadowClient* client = (rdpShadowClient*)input->context; + rdpShadowSubsystem* subsystem = client->server->subsystem; + + if (!client->mayInteract) + return TRUE; + + return IFCALLRESULT(TRUE, subsystem->KeyboardEvent, subsystem, client, flags, code); +} + +static BOOL shadow_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code) +{ + rdpShadowClient* client = (rdpShadowClient*)input->context; + rdpShadowSubsystem* subsystem = client->server->subsystem; + + if (!client->mayInteract) + return TRUE; + + return IFCALLRESULT(TRUE, subsystem->UnicodeKeyboardEvent, subsystem, client, flags, code); +} + +static BOOL shadow_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + rdpShadowClient* client = (rdpShadowClient*)input->context; + rdpShadowSubsystem* subsystem = client->server->subsystem; + + if (client->server->shareSubRect) + { + x += client->server->subRect.left; + y += client->server->subRect.top; + } + + if (!(flags & PTR_FLAGS_WHEEL)) + { + client->pointerX = x; + client->pointerY = y; + + if ((client->pointerX == subsystem->pointerX) && (client->pointerY == subsystem->pointerY)) + { + flags &= ~PTR_FLAGS_MOVE; + + if (!(flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3))) + return TRUE; + } + } + + if (!client->mayInteract) + return TRUE; + + return IFCALLRESULT(TRUE, subsystem->MouseEvent, subsystem, client, flags, x, y); +} + +static BOOL shadow_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y) +{ + rdpShadowClient* client = (rdpShadowClient*)input->context; + rdpShadowSubsystem* subsystem = client->server->subsystem; + + if (client->server->shareSubRect) + { + x += client->server->subRect.left; + y += client->server->subRect.top; + } + + client->pointerX = x; + client->pointerY = y; + + if (!client->mayInteract) + return TRUE; + + return IFCALLRESULT(TRUE, subsystem->ExtendedMouseEvent, subsystem, client, flags, x, y); +} + +void shadow_input_register_callbacks(rdpInput* input) +{ + input->SynchronizeEvent = shadow_input_synchronize_event; + input->KeyboardEvent = shadow_input_keyboard_event; + input->UnicodeKeyboardEvent = shadow_input_unicode_keyboard_event; + input->MouseEvent = shadow_input_mouse_event; + input->ExtendedMouseEvent = shadow_input_extended_mouse_event; +} diff --git a/server/shadow/shadow_input.h b/server/shadow/shadow_input.h new file mode 100644 index 0000000..8bde31c --- /dev/null +++ b/server/shadow/shadow_input.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_INPUT_H +#define FREERDP_SERVER_SHADOW_INPUT_H + +#include <freerdp/server/shadow.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + void shadow_input_register_callbacks(rdpInput* input); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_INPUT_H */ diff --git a/server/shadow/shadow_lobby.c b/server/shadow/shadow_lobby.c new file mode 100644 index 0000000..8a9fade --- /dev/null +++ b/server/shadow/shadow_lobby.c @@ -0,0 +1,85 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/assert.h> +#include <rdtk/rdtk.h> + +#include "shadow.h" + +#include "shadow_lobby.h" + +BOOL shadow_client_init_lobby(rdpShadowServer* server) +{ + int width = 0; + int height = 0; + rdtkEngine* engine = NULL; + rdtkSurface* surface = NULL; + RECTANGLE_16 invalidRect; + rdpShadowSurface* lobby = server->lobby; + + if (!lobby) + return FALSE; + + if (!(engine = rdtk_engine_new())) + { + return FALSE; + } + + if (!(surface = + rdtk_surface_new(engine, lobby->data, lobby->width, lobby->height, lobby->scanline))) + { + rdtk_engine_free(engine); + return FALSE; + } + + invalidRect.left = 0; + invalidRect.top = 0; + WINPR_ASSERT(lobby->width <= UINT16_MAX); + WINPR_ASSERT(lobby->height <= UINT16_MAX); + invalidRect.right = (UINT16)lobby->width; + invalidRect.bottom = (UINT16)lobby->height; + if (server->shareSubRect) + { + /* If we have shared sub rect setting, only fill shared rect */ + rectangles_intersection(&invalidRect, &(server->subRect), &invalidRect); + } + + width = invalidRect.right - invalidRect.left; + height = invalidRect.bottom - invalidRect.top; + WINPR_ASSERT(width <= UINT16_MAX); + WINPR_ASSERT(width >= 0); + WINPR_ASSERT(height <= UINT16_MAX); + WINPR_ASSERT(height >= 0); + rdtk_surface_fill(surface, invalidRect.left, invalidRect.top, (UINT16)width, (UINT16)height, + 0x3BB9FF); + + rdtk_label_draw(surface, invalidRect.left, invalidRect.top, (UINT16)width, (UINT16)height, NULL, + "Welcome", 0, 0); + // rdtk_button_draw(surface, 16, 64, 128, 32, NULL, "button"); + // rdtk_text_field_draw(surface, 16, 128, 128, 32, NULL, "text field"); + + rdtk_surface_free(surface); + + rdtk_engine_free(engine); + + region16_union_rect(&(lobby->invalidRegion), &(lobby->invalidRegion), &invalidRect); + + return TRUE; +} diff --git a/server/shadow/shadow_lobby.h b/server/shadow/shadow_lobby.h new file mode 100644 index 0000000..37ad9cf --- /dev/null +++ b/server/shadow/shadow_lobby.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_LOBBY_H +#define FREERDP_SERVER_SHADOW_LOBBY_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#include <rdtk/rdtk.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + BOOL shadow_client_init_lobby(rdpShadowServer* server); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_LOBBY_H */ diff --git a/server/shadow/shadow_mcevent.c b/server/shadow/shadow_mcevent.c new file mode 100644 index 0000000..4609cee --- /dev/null +++ b/server/shadow/shadow_mcevent.c @@ -0,0 +1,355 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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 <freerdp/config.h> + +#include <winpr/assert.h> +#include <freerdp/log.h> +#include "shadow.h" + +#define TAG SERVER_TAG("shadow.mcevent") + +struct rdp_shadow_multiclient_event +{ + HANDLE event; /* Kickoff event */ + HANDLE barrierEvent; /* Represents that all clients have consumed event */ + HANDLE doneEvent; /* Event handling finished. Server could continue */ + wArrayList* subscribers; + CRITICAL_SECTION lock; + int consuming; + int waiting; + + /* For debug */ + int eventid; +}; + +struct rdp_shadow_multiclient_subscriber +{ + rdpShadowMultiClientEvent* ref; + BOOL pleaseHandle; /* Indicate if server expects my handling in this turn */ +}; + +rdpShadowMultiClientEvent* shadow_multiclient_new(void) +{ + rdpShadowMultiClientEvent* event = + (rdpShadowMultiClientEvent*)calloc(1, sizeof(rdpShadowMultiClientEvent)); + if (!event) + goto out_error; + + event->event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!event->event) + goto out_free; + + event->barrierEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!event->barrierEvent) + goto out_free_event; + + event->doneEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!event->doneEvent) + goto out_free_barrierEvent; + + event->subscribers = ArrayList_New(TRUE); + if (!event->subscribers) + goto out_free_doneEvent; + + if (!InitializeCriticalSectionAndSpinCount(&(event->lock), 4000)) + goto out_free_subscribers; + + event->consuming = 0; + event->waiting = 0; + event->eventid = 0; + SetEvent(event->doneEvent); + return event; + +out_free_subscribers: + ArrayList_Free(event->subscribers); +out_free_doneEvent: + CloseHandle(event->doneEvent); +out_free_barrierEvent: + CloseHandle(event->barrierEvent); +out_free_event: + CloseHandle(event->event); +out_free: + free(event); +out_error: + return (rdpShadowMultiClientEvent*)NULL; +} + +void shadow_multiclient_free(rdpShadowMultiClientEvent* event) +{ + if (!event) + return; + + DeleteCriticalSection(&(event->lock)); + + ArrayList_Free(event->subscribers); + CloseHandle(event->doneEvent); + CloseHandle(event->barrierEvent); + CloseHandle(event->event); + free(event); + + return; +} + +static void _Publish(rdpShadowMultiClientEvent* event) +{ + wArrayList* subscribers = NULL; + struct rdp_shadow_multiclient_subscriber* subscriber = NULL; + + subscribers = event->subscribers; + + WINPR_ASSERT(event->consuming == 0); + + /* Count subscribing clients */ + ArrayList_Lock(subscribers); + for (size_t i = 0; i < ArrayList_Count(subscribers); i++) + { + subscriber = (struct rdp_shadow_multiclient_subscriber*)ArrayList_GetItem(subscribers, i); + /* Set flag to subscriber: I acknowledge and please handle */ + subscriber->pleaseHandle = TRUE; + event->consuming++; + } + ArrayList_Unlock(subscribers); + + if (event->consuming > 0) + { + event->eventid = (event->eventid & 0xff) + 1; + WLog_VRB(TAG, "Server published event %d. %d clients.\n", event->eventid, event->consuming); + ResetEvent(event->doneEvent); + SetEvent(event->event); + } + + return; +} + +static void _WaitForSubscribers(rdpShadowMultiClientEvent* event) +{ + if (event->consuming > 0) + { + /* Wait for clients done */ + WLog_VRB(TAG, "Server wait event %d. %d clients.\n", event->eventid, event->consuming); + LeaveCriticalSection(&(event->lock)); + WaitForSingleObject(event->doneEvent, INFINITE); + EnterCriticalSection(&(event->lock)); + WLog_VRB(TAG, "Server quit event %d. %d clients.\n", event->eventid, event->consuming); + } + + /* Last subscriber should have already reset the event */ + WINPR_ASSERT(WaitForSingleObject(event->event, 0) != WAIT_OBJECT_0); + + return; +} + +void shadow_multiclient_publish(rdpShadowMultiClientEvent* event) +{ + if (!event) + return; + + EnterCriticalSection(&(event->lock)); + _Publish(event); + LeaveCriticalSection(&(event->lock)); + + return; +} +void shadow_multiclient_wait(rdpShadowMultiClientEvent* event) +{ + if (!event) + return; + + EnterCriticalSection(&(event->lock)); + _WaitForSubscribers(event); + LeaveCriticalSection(&(event->lock)); + + return; +} +void shadow_multiclient_publish_and_wait(rdpShadowMultiClientEvent* event) +{ + if (!event) + return; + + EnterCriticalSection(&(event->lock)); + _Publish(event); + _WaitForSubscribers(event); + LeaveCriticalSection(&(event->lock)); + + return; +} + +static BOOL _Consume(struct rdp_shadow_multiclient_subscriber* subscriber, BOOL wait) +{ + rdpShadowMultiClientEvent* event = subscriber->ref; + BOOL ret = FALSE; + + if (WaitForSingleObject(event->event, 0) == WAIT_OBJECT_0 && subscriber->pleaseHandle) + { + /* Consume my share. Server is waiting for us */ + event->consuming--; + ret = TRUE; + } + + WINPR_ASSERT(event->consuming >= 0); + + if (event->consuming == 0) + { + /* Last client reset event before notify clients to continue */ + ResetEvent(event->event); + + if (event->waiting > 0) + { + /* Notify other clients to continue */ + SetEvent(event->barrierEvent); + } + else + { + /* Only one client. Notify server directly */ + SetEvent(event->doneEvent); + } + } + else /* (event->consuming > 0) */ + { + if (wait) + { + /* + * This client need to wait. That means the client will + * continue waiting for other clients to finish. + * The last client should reset barrierEvent. + */ + event->waiting++; + LeaveCriticalSection(&(event->lock)); + WaitForSingleObject(event->barrierEvent, INFINITE); + EnterCriticalSection(&(event->lock)); + event->waiting--; + if (event->waiting == 0) + { + /* + * This is last client waiting for barrierEvent. + * We can now discard barrierEvent and notify + * server to continue. + */ + ResetEvent(event->barrierEvent); + SetEvent(event->doneEvent); + } + } + } + + return ret; +} + +void* shadow_multiclient_get_subscriber(rdpShadowMultiClientEvent* event) +{ + struct rdp_shadow_multiclient_subscriber* subscriber = NULL; + + if (!event) + return NULL; + + EnterCriticalSection(&(event->lock)); + + subscriber = (struct rdp_shadow_multiclient_subscriber*)calloc( + 1, sizeof(struct rdp_shadow_multiclient_subscriber)); + if (!subscriber) + goto out_error; + + subscriber->ref = event; + subscriber->pleaseHandle = FALSE; + + if (!ArrayList_Append(event->subscribers, subscriber)) + goto out_free; + + WLog_VRB(TAG, "Get subscriber %p. Wait event %d. %d clients.\n", (void*)subscriber, + event->eventid, event->consuming); + (void)_Consume(subscriber, TRUE); + WLog_VRB(TAG, "Get subscriber %p. Quit event %d. %d clients.\n", (void*)subscriber, + event->eventid, event->consuming); + + LeaveCriticalSection(&(event->lock)); + + return subscriber; + +out_free: + free(subscriber); +out_error: + LeaveCriticalSection(&(event->lock)); + return NULL; +} + +/* + * Consume my share and release my register + * If we have update event and pleaseHandle flag + * We need to consume. Anyway we need to clear + * pleaseHandle flag + */ +void shadow_multiclient_release_subscriber(void* subscriber) +{ + struct rdp_shadow_multiclient_subscriber* s = NULL; + rdpShadowMultiClientEvent* event = NULL; + + if (!subscriber) + return; + + s = (struct rdp_shadow_multiclient_subscriber*)subscriber; + event = s->ref; + + EnterCriticalSection(&(event->lock)); + + WLog_VRB(TAG, "Release Subscriber %p. Drop event %d. %d clients.\n", subscriber, event->eventid, + event->consuming); + (void)_Consume(s, FALSE); + WLog_VRB(TAG, "Release Subscriber %p. Quit event %d. %d clients.\n", subscriber, event->eventid, + event->consuming); + + ArrayList_Remove(event->subscribers, subscriber); + + LeaveCriticalSection(&(event->lock)); + + free(subscriber); + + return; +} + +BOOL shadow_multiclient_consume(void* subscriber) +{ + struct rdp_shadow_multiclient_subscriber* s = NULL; + rdpShadowMultiClientEvent* event = NULL; + BOOL ret = FALSE; + + if (!subscriber) + return ret; + + s = (struct rdp_shadow_multiclient_subscriber*)subscriber; + event = s->ref; + + EnterCriticalSection(&(event->lock)); + + WLog_VRB(TAG, "Subscriber %p wait event %d. %d clients.\n", subscriber, event->eventid, + event->consuming); + ret = _Consume(s, TRUE); + WLog_VRB(TAG, "Subscriber %p quit event %d. %d clients.\n", subscriber, event->eventid, + event->consuming); + + LeaveCriticalSection(&(event->lock)); + + return ret; +} + +HANDLE shadow_multiclient_getevent(void* subscriber) +{ + if (!subscriber) + return (HANDLE)NULL; + + return ((struct rdp_shadow_multiclient_subscriber*)subscriber)->ref->event; +} diff --git a/server/shadow/shadow_mcevent.h b/server/shadow/shadow_mcevent.h new file mode 100644 index 0000000..c78b920 --- /dev/null +++ b/server/shadow/shadow_mcevent.h @@ -0,0 +1,56 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_MCEVENT_H +#define FREERDP_SERVER_SHADOW_MCEVENT_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/collections.h> + +/* + * This file implemented a model that an event is consumed + * by multiple clients. All clients should wait others before continue + * Server should wait for all clients before continue + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + void shadow_multiclient_free(rdpShadowMultiClientEvent* event); + + WINPR_ATTR_MALLOC(shadow_multiclient_free, 1) + rdpShadowMultiClientEvent* shadow_multiclient_new(void); + + void shadow_multiclient_publish(rdpShadowMultiClientEvent* event); + void shadow_multiclient_wait(rdpShadowMultiClientEvent* event); + void shadow_multiclient_publish_and_wait(rdpShadowMultiClientEvent* event); + void* shadow_multiclient_get_subscriber(rdpShadowMultiClientEvent* event); + void shadow_multiclient_release_subscriber(void* subscriber); + BOOL shadow_multiclient_consume(void* subscriber); + HANDLE shadow_multiclient_getevent(void* subscriber); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_MCEVENT_H */ diff --git a/server/shadow/shadow_rdpgfx.c b/server/shadow/shadow_rdpgfx.c new file mode 100644 index 0000000..16bb236 --- /dev/null +++ b/server/shadow/shadow_rdpgfx.c @@ -0,0 +1,55 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.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 <freerdp/config.h> + +#include <freerdp/log.h> +#include "shadow.h" + +#include "shadow_rdpgfx.h" + +#define TAG SERVER_TAG("shadow") + +int shadow_client_rdpgfx_init(rdpShadowClient* client) +{ + WINPR_ASSERT(client); + + RdpgfxServerContext* rdpgfx = client->rdpgfx = rdpgfx_server_context_new(client->vcm); + if (!rdpgfx) + { + return 0; + } + + rdpgfx->rdpcontext = &client->context; + + rdpgfx->custom = client; + + if (!IFCALLRESULT(CHANNEL_RC_OK, rdpgfx->Initialize, rdpgfx, TRUE)) + return -1; + + return 1; +} + +void shadow_client_rdpgfx_uninit(rdpShadowClient* client) +{ + if (client->rdpgfx) + { + rdpgfx_server_context_free(client->rdpgfx); + client->rdpgfx = NULL; + } +} diff --git a/server/shadow/shadow_rdpgfx.h b/server/shadow/shadow_rdpgfx.h new file mode 100644 index 0000000..ce81376 --- /dev/null +++ b/server/shadow/shadow_rdpgfx.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_RDPGFX_H +#define FREERDP_SERVER_SHADOW_RDPGFX_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + int shadow_client_rdpgfx_init(rdpShadowClient* client); + void shadow_client_rdpgfx_uninit(rdpShadowClient* client); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_RDPGFX_H */ diff --git a/server/shadow/shadow_rdpsnd.c b/server/shadow/shadow_rdpsnd.c new file mode 100644 index 0000000..16899e4 --- /dev/null +++ b/server/shadow/shadow_rdpsnd.c @@ -0,0 +1,87 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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 <freerdp/config.h> + +#include <winpr/crt.h> +#include <freerdp/log.h> +#include <freerdp/codec/dsp.h> +#include <freerdp/server/server-common.h> + +#include "shadow.h" + +#include "shadow_rdpsnd.h" + +#define TAG SERVER_TAG("shadow") + +static void rdpsnd_activated(RdpsndServerContext* context) +{ + for (size_t i = 0; i < context->num_client_formats; i++) + { + for (size_t j = 0; j < context->num_server_formats; j++) + { + if (audio_format_compatible(&context->server_formats[j], &context->client_formats[i])) + { + context->SelectFormat(context, i); + return; + } + } + } + + WLog_ERR(TAG, "Could not agree on a audio format with the server\n"); +} + +int shadow_client_rdpsnd_init(rdpShadowClient* client) +{ + RdpsndServerContext* rdpsnd = NULL; + rdpsnd = client->rdpsnd = rdpsnd_server_context_new(client->vcm); + + if (!rdpsnd) + { + return 0; + } + + rdpsnd->data = client; + + if (client->subsystem->rdpsndFormats) + { + rdpsnd->server_formats = client->subsystem->rdpsndFormats; + rdpsnd->num_server_formats = client->subsystem->nRdpsndFormats; + } + else + { + rdpsnd->num_server_formats = server_rdpsnd_get_formats(&rdpsnd->server_formats); + } + + if (rdpsnd->num_server_formats > 0) + rdpsnd->src_format = &rdpsnd->server_formats[0]; + + rdpsnd->Activated = rdpsnd_activated; + rdpsnd->Initialize(rdpsnd, TRUE); + return 1; +} + +void shadow_client_rdpsnd_uninit(rdpShadowClient* client) +{ + if (client->rdpsnd) + { + client->rdpsnd->Stop(client->rdpsnd); + rdpsnd_server_context_free(client->rdpsnd); + client->rdpsnd = NULL; + } +} diff --git a/server/shadow/shadow_rdpsnd.h b/server/shadow/shadow_rdpsnd.h new file mode 100644 index 0000000..ae34ea3 --- /dev/null +++ b/server/shadow/shadow_rdpsnd.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2015 Jiang Zihao <zihao.jiang@yahoo.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_RDPSND_H +#define FREERDP_SERVER_SHADOW_RDPSND_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + int shadow_client_rdpsnd_init(rdpShadowClient* client); + void shadow_client_rdpsnd_uninit(rdpShadowClient* client); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_RDPSND_H */ diff --git a/server/shadow/shadow_remdesk.c b/server/shadow/shadow_remdesk.c new file mode 100644 index 0000000..19fb9f3 --- /dev/null +++ b/server/shadow/shadow_remdesk.c @@ -0,0 +1,50 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * 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 <freerdp/config.h> + +#include "shadow.h" + +#include "shadow_remdesk.h" + +int shadow_client_remdesk_init(rdpShadowClient* client) +{ + RemdeskServerContext* remdesk = NULL; + + remdesk = client->remdesk = remdesk_server_context_new(client->vcm); + remdesk->rdpcontext = &client->context; + + remdesk->custom = (void*)client; + + if (client->remdesk) + client->remdesk->Start(client->remdesk); + + return 1; +} + +void shadow_client_remdesk_uninit(rdpShadowClient* client) +{ + if (client->remdesk) + { + client->remdesk->Stop(client->remdesk); + remdesk_server_context_free(client->remdesk); + client->remdesk = NULL; + } +} diff --git a/server/shadow/shadow_remdesk.h b/server/shadow/shadow_remdesk.h new file mode 100644 index 0000000..88fdba0 --- /dev/null +++ b/server/shadow/shadow_remdesk.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_REMDESK_H +#define FREERDP_SERVER_SHADOW_REMDESK_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + int shadow_client_remdesk_init(rdpShadowClient* client); + void shadow_client_remdesk_uninit(rdpShadowClient* client); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_REMDESK_H */ diff --git a/server/shadow/shadow_screen.c b/server/shadow/shadow_screen.c new file mode 100644 index 0000000..8a9e0b3 --- /dev/null +++ b/server/shadow/shadow_screen.c @@ -0,0 +1,163 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include <winpr/assert.h> + +#include "shadow_surface.h" + +#include "shadow_screen.h" +#include "shadow_lobby.h" + +rdpShadowScreen* shadow_screen_new(rdpShadowServer* server) +{ + WINPR_ASSERT(server); + WINPR_ASSERT(server->subsystem); + + rdpShadowScreen* screen = (rdpShadowScreen*)calloc(1, sizeof(rdpShadowScreen)); + + if (!screen) + goto fail; + + screen->server = server; + rdpShadowSubsystem* subsystem = server->subsystem; + + if (!InitializeCriticalSectionAndSpinCount(&(screen->lock), 4000)) + goto fail; + + region16_init(&(screen->invalidRegion)); + + WINPR_ASSERT(subsystem->selectedMonitor < ARRAYSIZE(subsystem->monitors)); + const MONITOR_DEF* primary = &(subsystem->monitors[subsystem->selectedMonitor]); + WINPR_ASSERT(primary); + + INT64 x = primary->left; + INT64 y = primary->top; + INT64 width = primary->right - primary->left + 1; + INT64 height = primary->bottom - primary->top + 1; + + WINPR_ASSERT(x >= 0); + WINPR_ASSERT(x <= UINT16_MAX); + WINPR_ASSERT(y >= 0); + WINPR_ASSERT(y <= UINT16_MAX); + WINPR_ASSERT(width >= 0); + WINPR_ASSERT(width <= UINT16_MAX); + WINPR_ASSERT(height >= 0); + WINPR_ASSERT(height <= UINT16_MAX); + + screen->width = (UINT16)width; + screen->height = (UINT16)height; + + screen->primary = + shadow_surface_new(server, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height); + + if (!screen->primary) + goto fail; + + server->surface = screen->primary; + + screen->lobby = shadow_surface_new(server, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height); + + if (!screen->lobby) + goto fail; + + server->lobby = screen->lobby; + + if (!shadow_client_init_lobby(server)) + goto fail; + + return screen; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + shadow_screen_free(screen); + WINPR_PRAGMA_DIAG_POP + + return NULL; +} + +void shadow_screen_free(rdpShadowScreen* screen) +{ + if (!screen) + return; + + DeleteCriticalSection(&(screen->lock)); + + region16_uninit(&(screen->invalidRegion)); + + if (screen->primary) + { + shadow_surface_free(screen->primary); + screen->primary = NULL; + } + + if (screen->lobby) + { + shadow_surface_free(screen->lobby); + screen->lobby = NULL; + } + + free(screen); +} + +BOOL shadow_screen_resize(rdpShadowScreen* screen) +{ + int x = 0; + int y = 0; + int width = 0; + int height = 0; + MONITOR_DEF* primary = NULL; + rdpShadowSubsystem* subsystem = NULL; + + if (!screen) + return FALSE; + + subsystem = screen->server->subsystem; + primary = &(subsystem->monitors[subsystem->selectedMonitor]); + + x = primary->left; + y = primary->top; + width = primary->right - primary->left + 1; + height = primary->bottom - primary->top + 1; + + WINPR_ASSERT(x >= 0); + WINPR_ASSERT(x <= UINT16_MAX); + WINPR_ASSERT(y >= 0); + WINPR_ASSERT(y <= UINT16_MAX); + WINPR_ASSERT(width >= 0); + WINPR_ASSERT(width <= UINT16_MAX); + WINPR_ASSERT(height >= 0); + WINPR_ASSERT(height <= UINT16_MAX); + if (shadow_surface_resize(screen->primary, (UINT16)x, (UINT16)y, (UINT16)width, + (UINT16)height) && + shadow_surface_resize(screen->lobby, (UINT16)x, (UINT16)y, (UINT16)width, (UINT16)height)) + { + if (((UINT32)width != screen->width) || ((UINT32)height != screen->height)) + { + /* screen size is changed. Store new size and reinit lobby */ + screen->width = (UINT32)width; + screen->height = (UINT32)height; + shadow_client_init_lobby(screen->server); + } + return TRUE; + } + + return FALSE; +} diff --git a/server/shadow/shadow_screen.h b/server/shadow/shadow_screen.h new file mode 100644 index 0000000..a7bf789 --- /dev/null +++ b/server/shadow/shadow_screen.h @@ -0,0 +1,55 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_SCREEN_H +#define FREERDP_SERVER_SHADOW_SCREEN_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +struct rdp_shadow_screen +{ + rdpShadowServer* server; + + UINT32 width; + UINT32 height; + + CRITICAL_SECTION lock; + REGION16 invalidRegion; + + rdpShadowSurface* primary; + rdpShadowSurface* lobby; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + + void shadow_screen_free(rdpShadowScreen* screen); + + WINPR_ATTR_MALLOC(shadow_screen_free, 1) + rdpShadowScreen* shadow_screen_new(rdpShadowServer* server); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_SCREEN_H */ diff --git a/server/shadow/shadow_server.c b/server/shadow/shadow_server.c new file mode 100644 index 0000000..067dcc6 --- /dev/null +++ b/server/shadow/shadow_server.c @@ -0,0 +1,1028 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 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 <errno.h> + +#include <winpr/crt.h> +#include <winpr/ssl.h> +#include <winpr/path.h> +#include <winpr/cmdline.h> +#include <winpr/winsock.h> + +#include <freerdp/log.h> +#include <freerdp/version.h> + +#include <winpr/tools/makecert.h> + +#ifndef _WIN32 +#include <sys/select.h> +#include <signal.h> +#endif + +#include "shadow.h" + +#define TAG SERVER_TAG("shadow") + +static const char bind_address[] = "bind-address,"; + +static int shadow_server_print_command_line_help(int argc, char** argv, + COMMAND_LINE_ARGUMENT_A* largs) +{ + char* str = NULL; + size_t length = 0; + const COMMAND_LINE_ARGUMENT_A* arg = NULL; + if ((argc < 1) || !largs || !argv) + return -1; + + printf("Usage: %s [options]\n", argv[0]); + printf("\n"); + printf("Syntax:\n"); + printf(" /flag (enables flag)\n"); + printf(" /option:<value> (specifies option with value)\n"); + printf(" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n"); + printf("\n"); + arg = largs; + + do + { + if (arg->Flags & COMMAND_LINE_VALUE_FLAG) + { + printf(" %s", "/"); + printf("%-20s\n", arg->Name); + printf("\t%s\n", arg->Text); + } + else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) || + (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL)) + { + printf(" %s", "/"); + + if (arg->Format) + { + length = (strlen(arg->Name) + strlen(arg->Format) + 2); + str = (char*)malloc(length + 1); + + if (!str) + return -1; + + sprintf_s(str, length + 1, "%s:%s", arg->Name, arg->Format); + printf("%-20s\n", str); + free(str); + } + else + { + printf("%-20s\n", arg->Name); + } + + printf("\t%s\n", arg->Text); + } + else if (arg->Flags & COMMAND_LINE_VALUE_BOOL) + { + length = strlen(arg->Name) + 32; + str = (char*)malloc(length + 1); + + if (!str) + return -1; + + sprintf_s(str, length + 1, "%s (default:%s)", arg->Name, arg->Default ? "on" : "off"); + printf(" %s", arg->Default ? "-" : "+"); + printf("%-20s\n", str); + free(str); + printf("\t%s\n", arg->Text); + } + } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + return 1; +} + +int shadow_server_command_line_status_print(rdpShadowServer* server, int argc, char** argv, + int status, COMMAND_LINE_ARGUMENT_A* cargs) +{ + WINPR_UNUSED(server); + + if (status == COMMAND_LINE_STATUS_PRINT_VERSION) + { + printf("FreeRDP version %s (git %s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION); + return COMMAND_LINE_STATUS_PRINT_VERSION; + } + else if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG) + { + printf("%s\n", freerdp_get_build_config()); + return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG; + } + else if (status == COMMAND_LINE_STATUS_PRINT) + { + return COMMAND_LINE_STATUS_PRINT; + } + else if (status < 0) + { + if (shadow_server_print_command_line_help(argc, argv, cargs) < 0) + return -1; + + return COMMAND_LINE_STATUS_PRINT_HELP; + } + + return 1; +} + +int shadow_server_parse_command_line(rdpShadowServer* server, int argc, char** argv, + COMMAND_LINE_ARGUMENT_A* cargs) +{ + int status = 0; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = NULL; + rdpSettings* settings = server->settings; + + if ((argc < 2) || !argv || !cargs) + return 1; + + CommandLineClearArgumentsA(cargs); + flags = COMMAND_LINE_SEPARATOR_COLON; + flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS; + status = CommandLineParseArgumentsA(argc, argv, cargs, flags, server, NULL, NULL); + + if (status < 0) + return status; + + arg = cargs; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "port") + { + long val = strtol(arg->Value, NULL, 0); + + if ((errno != 0) || (val <= 0) || (val > UINT16_MAX)) + return -1; + + server->port = (DWORD)val; + } + CommandLineSwitchCase(arg, "ipc-socket") + { + /* /bind-address is incompatible */ + if (server->ipcSocket) + return -1; + server->ipcSocket = _strdup(arg->Value); + + if (!server->ipcSocket) + return -1; + } + CommandLineSwitchCase(arg, "bind-address") + { + int rc = 0; + size_t len = strlen(arg->Value) + sizeof(bind_address); + /* /ipc-socket is incompatible */ + if (server->ipcSocket) + return -1; + server->ipcSocket = calloc(len, sizeof(CHAR)); + + if (!server->ipcSocket) + return -1; + + rc = _snprintf(server->ipcSocket, len, "%s%s", bind_address, arg->Value); + if ((rc < 0) || ((size_t)rc != len - 1)) + return -1; + } + CommandLineSwitchCase(arg, "may-view") + { + server->mayView = arg->Value ? TRUE : FALSE; + } + CommandLineSwitchCase(arg, "may-interact") + { + server->mayInteract = arg->Value ? TRUE : FALSE; + } + CommandLineSwitchCase(arg, "max-connections") + { + errno = 0; + unsigned long val = strtoul(arg->Value, NULL, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return -1; + server->maxClientsConnected = val; + } + CommandLineSwitchCase(arg, "rect") + { + char* p = NULL; + char* tok[4]; + long x = -1; + long y = -1; + long w = -1; + long h = -1; + char* str = _strdup(arg->Value); + + if (!str) + return -1; + + tok[0] = p = str; + p = strchr(p + 1, ','); + + if (!p) + { + free(str); + return -1; + } + + *p++ = '\0'; + tok[1] = p; + p = strchr(p + 1, ','); + + if (!p) + { + free(str); + return -1; + } + + *p++ = '\0'; + tok[2] = p; + p = strchr(p + 1, ','); + + if (!p) + { + free(str); + return -1; + } + + *p++ = '\0'; + tok[3] = p; + x = strtol(tok[0], NULL, 0); + + if (errno != 0) + goto fail; + + y = strtol(tok[1], NULL, 0); + + if (errno != 0) + goto fail; + + w = strtol(tok[2], NULL, 0); + + if (errno != 0) + goto fail; + + h = strtol(tok[3], NULL, 0); + + if (errno != 0) + goto fail; + + fail: + free(str); + + if ((x < 0) || (y < 0) || (w < 1) || (h < 1) || (errno != 0)) + return -1; + + if ((x > UINT16_MAX) || (y > UINT16_MAX) || (x + w > UINT16_MAX) || + (y + h > UINT16_MAX)) + return -1; + server->subRect.left = (UINT16)x; + server->subRect.top = (UINT16)y; + server->subRect.right = (UINT16)(x + w); + server->subRect.bottom = (UINT16)(y + h); + server->shareSubRect = TRUE; + } + CommandLineSwitchCase(arg, "auth") + { + server->authentication = arg->Value ? TRUE : FALSE; + } + CommandLineSwitchCase(arg, "remote-guard") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteCredentialGuard, + arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "sec") + { + if (strcmp("rdp", arg->Value) == 0) /* Standard RDP */ + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE)) + return COMMAND_LINE_ERROR; + } + else if (strcmp("tls", arg->Value) == 0) /* TLS */ + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE)) + return COMMAND_LINE_ERROR; + } + else if (strcmp("nla", arg->Value) == 0) /* NLA */ + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE)) + return COMMAND_LINE_ERROR; + } + else if (strcmp("ext", arg->Value) == 0) /* NLA Extended */ + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, TRUE)) + return COMMAND_LINE_ERROR; + } + else + { + WLog_ERR(TAG, "unknown protocol security: %s", arg->Value); + } + } + CommandLineSwitchCase(arg, "sec-rdp") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, + arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "sec-tls") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, + arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "sec-nla") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, + arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "sec-ext") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, + arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "sam-file") + { + freerdp_settings_set_string(settings, FreeRDP_NtlmSamFile, arg->Value); + } + CommandLineSwitchCase(arg, "log-level") + { + wLog* root = WLog_GetRoot(); + + if (!WLog_SetStringLogLevel(root, arg->Value)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "log-filters") + { + if (!WLog_AddStringLogFilters(arg->Value)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "gfx-progressive") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, + arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "gfx-rfx") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, + arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "gfx-planar") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxPlanar, arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "gfx-avc420") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "gfx-avc444") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, + arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, arg->Value ? TRUE : FALSE)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "keytab") + { + if (!freerdp_settings_set_string(settings, FreeRDP_KerberosKeytab, arg->Value)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "ccache") + { + if (!freerdp_settings_set_string(settings, FreeRDP_KerberosCache, arg->Value)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchCase(arg, "tls-secrets-file") + { + if (!freerdp_settings_set_string(settings, FreeRDP_TlsSecretsFile, arg->Value)) + return COMMAND_LINE_ERROR; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL); + + arg = CommandLineFindArgumentA(cargs, "monitors"); + + if (arg && (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + { + UINT32 numMonitors = 0; + MONITOR_DEF monitors[16] = { 0 }; + numMonitors = shadow_enum_monitors(monitors, 16); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + /* Select monitors */ + long val = strtol(arg->Value, NULL, 0); + + if ((val < 0) || (errno != 0) || ((UINT32)val >= numMonitors)) + status = COMMAND_LINE_STATUS_PRINT; + + server->selectedMonitor = (UINT32)val; + } + else + { + /* List monitors */ + + for (UINT32 index = 0; index < numMonitors; index++) + { + const MONITOR_DEF* monitor = &monitors[index]; + const INT64 width = monitor->right - monitor->left + 1; + const INT64 height = monitor->bottom - monitor->top + 1; + WLog_INFO(TAG, " %s [%d] %" PRId64 "x%" PRId64 "\t+%" PRId32 "+%" PRId32 "", + (monitor->flags == 1) ? "*" : " ", index, width, height, monitor->left, + monitor->top); + } + + status = COMMAND_LINE_STATUS_PRINT; + } + } + + /* If we want to disable authentication we need to ensure that NLA security + * is not activated. Only TLS and RDP security allow anonymous login. + */ + if (!server->authentication) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + return COMMAND_LINE_ERROR; + } + return status; +} + +static DWORD WINAPI shadow_server_thread(LPVOID arg) +{ + rdpShadowServer* server = (rdpShadowServer*)arg; + BOOL running = TRUE; + DWORD status = 0; + freerdp_listener* listener = server->listener; + shadow_subsystem_start(server->subsystem); + + while (running) + { + HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 }; + DWORD nCount = 0; + events[nCount++] = server->StopEvent; + nCount += listener->GetEventHandles(listener, &events[nCount], ARRAYSIZE(events) - nCount); + + if (nCount <= 1) + { + WLog_ERR(TAG, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + switch (status) + { + case WAIT_FAILED: + case WAIT_OBJECT_0: + running = FALSE; + break; + + default: + { + if (!listener->CheckFileDescriptor(listener)) + { + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + running = FALSE; + } + else + { +#ifdef _WIN32 + Sleep(100); /* FIXME: listener event handles */ +#endif + } + } + break; + } + } + + listener->Close(listener); + shadow_subsystem_stop(server->subsystem); + + /* Signal to the clients that server is being stopped and wait for them + * to disconnect. */ + if (shadow_client_boardcast_quit(server, 0)) + { + while (ArrayList_Count(server->clients) > 0) + { + Sleep(100); + } + } + + ExitThread(0); + return 0; +} + +static BOOL open_port(rdpShadowServer* server, char* address) +{ + BOOL status = 0; + char* modaddr = address; + + if (modaddr) + { + if (modaddr[0] == '[') + { + char* end = strchr(address, ']'); + if (!end) + { + WLog_ERR(TAG, "Could not parse bind-address %s", address); + return -1; + } + *end++ = '\0'; + if (strlen(end) > 0) + { + WLog_ERR(TAG, "Excess data after IPv6 address: '%s'", end); + return -1; + } + modaddr++; + } + } + status = server->listener->Open(server->listener, modaddr, (UINT16)server->port); + + if (!status) + { + WLog_ERR(TAG, + "Problem creating TCP listener. (Port already used or insufficient permissions?)"); + } + + return status; +} + +int shadow_server_start(rdpShadowServer* server) +{ + BOOL ipc = 0; + BOOL status = 0; + WSADATA wsaData; + + if (!server) + return -1; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + return -1; + +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + server->screen = shadow_screen_new(server); + + if (!server->screen) + { + WLog_ERR(TAG, "screen_new failed"); + return -1; + } + + server->capture = shadow_capture_new(server); + + if (!server->capture) + { + WLog_ERR(TAG, "capture_new failed"); + return -1; + } + + /* Bind magic: + * + * emtpy ... bind TCP all + * <local path> ... bind local (IPC) + * bind-socket,<address> ... bind TCP to specified interface + */ + ipc = server->ipcSocket && (strncmp(bind_address, server->ipcSocket, + strnlen(bind_address, sizeof(bind_address))) != 0); + if (!ipc) + { + size_t count = 0; + char** list = CommandLineParseCommaSeparatedValuesEx(NULL, server->ipcSocket, &count); + if (!list || (count <= 1)) + { + if (server->ipcSocket == NULL) + { + if (!open_port(server, NULL)) + { + free(list); + return -1; + } + } + else + { + free(list); + return -1; + } + } + + WINPR_ASSERT(list || (count == 0)); + for (size_t x = 1; x < count; x++) + { + BOOL success = open_port(server, list[x]); + if (!success) + { + free(list); + return -1; + } + } + free(list); + } + else + { + status = server->listener->OpenLocal(server->listener, server->ipcSocket); + + if (!status) + { + WLog_ERR(TAG, "Problem creating local socket listener. (Port already used or " + "insufficient permissions?)"); + return -1; + } + } + + if (!(server->thread = CreateThread(NULL, 0, shadow_server_thread, (void*)server, 0, NULL))) + { + return -1; + } + + return 0; +} + +int shadow_server_stop(rdpShadowServer* server) +{ + if (!server) + return -1; + + if (server->thread) + { + SetEvent(server->StopEvent); + WaitForSingleObject(server->thread, INFINITE); + CloseHandle(server->thread); + server->thread = NULL; + if (server->listener && server->listener->Close) + server->listener->Close(server->listener); + } + + if (server->screen) + { + shadow_screen_free(server->screen); + server->screen = NULL; + } + + if (server->capture) + { + shadow_capture_free(server->capture); + server->capture = NULL; + } + + return 0; +} + +static int shadow_server_init_config_path(rdpShadowServer* server) +{ +#ifdef _WIN32 + + if (!server->ConfigPath) + { + server->ConfigPath = GetEnvironmentSubPath("LOCALAPPDATA", "freerdp"); + } + +#endif +#ifdef __APPLE__ + + if (!server->ConfigPath) + { + char* userLibraryPath; + char* userApplicationSupportPath; + userLibraryPath = GetKnownSubPath(KNOWN_PATH_HOME, "Library"); + + if (userLibraryPath) + { + if (!winpr_PathFileExists(userLibraryPath) && !winpr_PathMakePath(userLibraryPath, 0)) + { + WLog_ERR(TAG, "Failed to create directory '%s'", userLibraryPath); + free(userLibraryPath); + return -1; + } + + userApplicationSupportPath = GetCombinedPath(userLibraryPath, "Application Support"); + + if (userApplicationSupportPath) + { + if (!winpr_PathFileExists(userApplicationSupportPath) && + !winpr_PathMakePath(userApplicationSupportPath, 0)) + { + WLog_ERR(TAG, "Failed to create directory '%s'", userApplicationSupportPath); + free(userLibraryPath); + free(userApplicationSupportPath); + return -1; + } + + server->ConfigPath = GetCombinedPath(userApplicationSupportPath, "freerdp"); + } + + free(userLibraryPath); + free(userApplicationSupportPath); + } + } + +#endif + + if (!server->ConfigPath) + { + char* configHome = NULL; + configHome = GetKnownPath(KNOWN_PATH_XDG_CONFIG_HOME); + + if (configHome) + { + if (!winpr_PathFileExists(configHome) && !winpr_PathMakePath(configHome, 0)) + { + WLog_ERR(TAG, "Failed to create directory '%s'", configHome); + free(configHome); + return -1; + } + + server->ConfigPath = GetKnownSubPath(KNOWN_PATH_XDG_CONFIG_HOME, "freerdp"); + free(configHome); + } + } + + if (!server->ConfigPath) + return -1; /* no usable config path */ + + return 1; +} + +static BOOL shadow_server_create_certificate(rdpShadowServer* server, const char* filepath) +{ + BOOL rc = FALSE; + char* makecert_argv[6] = { "makecert", "-rdp", "-live", "-silent", "-y", "5" }; + const size_t makecert_argc = ARRAYSIZE(makecert_argv); + + MAKECERT_CONTEXT* makecert = makecert_context_new(); + + if (!makecert) + goto out_fail; + + if (makecert_context_process(makecert, makecert_argc, makecert_argv) < 0) + goto out_fail; + + if (makecert_context_set_output_file_name(makecert, "shadow") != 1) + goto out_fail; + + WINPR_ASSERT(server); + WINPR_ASSERT(filepath); + if (!winpr_PathFileExists(server->CertificateFile)) + { + if (makecert_context_output_certificate_file(makecert, filepath) != 1) + goto out_fail; + } + + if (!winpr_PathFileExists(server->PrivateKeyFile)) + { + if (makecert_context_output_private_key_file(makecert, filepath) != 1) + goto out_fail; + } + rc = TRUE; +out_fail: + makecert_context_free(makecert); + return rc; +} +static BOOL shadow_server_init_certificate(rdpShadowServer* server) +{ + char* filepath = NULL; + BOOL ret = FALSE; + + WINPR_ASSERT(server); + + if (!winpr_PathFileExists(server->ConfigPath) && !winpr_PathMakePath(server->ConfigPath, 0)) + { + WLog_ERR(TAG, "Failed to create directory '%s'", server->ConfigPath); + return FALSE; + } + + if (!(filepath = GetCombinedPath(server->ConfigPath, "shadow"))) + return FALSE; + + if (!winpr_PathFileExists(filepath) && !winpr_PathMakePath(filepath, 0)) + { + if (!CreateDirectoryA(filepath, 0)) + { + WLog_ERR(TAG, "Failed to create directory '%s'", filepath); + goto out_fail; + } + } + + server->CertificateFile = GetCombinedPath(filepath, "shadow.crt"); + server->PrivateKeyFile = GetCombinedPath(filepath, "shadow.key"); + + if (!server->CertificateFile || !server->PrivateKeyFile) + goto out_fail; + + if ((!winpr_PathFileExists(server->CertificateFile)) || + (!winpr_PathFileExists(server->PrivateKeyFile))) + { + if (!shadow_server_create_certificate(server, filepath)) + goto out_fail; + } + + rdpSettings* settings = server->settings; + WINPR_ASSERT(settings); + + rdpPrivateKey* key = freerdp_key_new_from_file(server->PrivateKeyFile); + if (!key) + goto out_fail; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1)) + goto out_fail; + + rdpCertificate* cert = freerdp_certificate_new_from_file(server->CertificateFile); + if (!cert) + goto out_fail; + + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1)) + goto out_fail; + + if (!freerdp_certificate_is_rdp_security_compatible(cert)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE)) + goto out_fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE)) + goto out_fail; + } + ret = TRUE; +out_fail: + free(filepath); + return ret; +} + +static BOOL shadow_server_check_peer_restrictions(freerdp_listener* listener) +{ + WINPR_ASSERT(listener); + + rdpShadowServer* server = (rdpShadowServer*)listener->info; + WINPR_ASSERT(server); + + if (server->maxClientsConnected > 0) + { + const size_t count = ArrayList_Count(server->clients); + if (count >= server->maxClientsConnected) + { + WLog_WARN(TAG, "connection limit [%" PRIuz "] reached, discarding client", + server->maxClientsConnected); + return FALSE; + } + } + return TRUE; +} + +int shadow_server_init(rdpShadowServer* server) +{ + int status = 0; + winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT); + WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()); + + if (!(server->clients = ArrayList_New(TRUE))) + goto fail; + + if (!(server->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + goto fail; + + if (!InitializeCriticalSectionAndSpinCount(&(server->lock), 4000)) + goto fail; + + status = shadow_server_init_config_path(server); + + if (status < 0) + goto fail; + + if (!shadow_server_init_certificate(server)) + goto fail; + + server->listener = freerdp_listener_new(); + + if (!server->listener) + goto fail; + + server->listener->info = (void*)server; + server->listener->CheckPeerAcceptRestrictions = shadow_server_check_peer_restrictions; + server->listener->PeerAccepted = shadow_client_accepted; + server->subsystem = shadow_subsystem_new(); + + if (!server->subsystem) + goto fail; + + status = shadow_subsystem_init(server->subsystem, server); + if (status < 0) + goto fail; + + return status; + +fail: + shadow_server_uninit(server); + WLog_ERR(TAG, "Failed to initialize shadow server"); + return -1; +} + +int shadow_server_uninit(rdpShadowServer* server) +{ + if (!server) + return -1; + + shadow_server_stop(server); + shadow_subsystem_uninit(server->subsystem); + shadow_subsystem_free(server->subsystem); + server->subsystem = NULL; + freerdp_listener_free(server->listener); + server->listener = NULL; + free(server->CertificateFile); + server->CertificateFile = NULL; + free(server->PrivateKeyFile); + server->PrivateKeyFile = NULL; + free(server->ConfigPath); + server->ConfigPath = NULL; + DeleteCriticalSection(&(server->lock)); + CloseHandle(server->StopEvent); + server->StopEvent = NULL; + ArrayList_Free(server->clients); + server->clients = NULL; + return 1; +} + +rdpShadowServer* shadow_server_new(void) +{ + rdpShadowServer* server = NULL; + server = (rdpShadowServer*)calloc(1, sizeof(rdpShadowServer)); + + if (!server) + return NULL; + + server->port = 3389; + server->mayView = TRUE; + server->mayInteract = TRUE; + server->rfxMode = RLGR3; + server->h264RateControlMode = H264_RATECONTROL_VBR; + server->h264BitRate = 10000000; + server->h264FrameRate = 30; + server->h264QP = 0; + server->authentication = TRUE; + server->settings = freerdp_settings_new(FREERDP_SETTINGS_SERVER_MODE); + return server; +} + +void shadow_server_free(rdpShadowServer* server) +{ + if (!server) + return; + + free(server->ipcSocket); + server->ipcSocket = NULL; + freerdp_settings_free(server->settings); + server->settings = NULL; + free(server); +} diff --git a/server/shadow/shadow_subsystem.c b/server/shadow/shadow_subsystem.c new file mode 100644 index 0000000..ca73c72 --- /dev/null +++ b/server/shadow/shadow_subsystem.c @@ -0,0 +1,286 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include "shadow.h" + +#include "shadow_subsystem.h" + +static pfnShadowSubsystemEntry pSubsystemEntry = NULL; + +void shadow_subsystem_set_entry(pfnShadowSubsystemEntry pEntry) +{ + pSubsystemEntry = pEntry; +} + +static int shadow_subsystem_load_entry_points(RDP_SHADOW_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + ZeroMemory(pEntryPoints, sizeof(RDP_SHADOW_ENTRY_POINTS)); + + if (!pSubsystemEntry) + return -1; + + if (pSubsystemEntry(pEntryPoints) < 0) + return -1; + + return 1; +} + +rdpShadowSubsystem* shadow_subsystem_new(void) +{ + RDP_SHADOW_ENTRY_POINTS ep; + rdpShadowSubsystem* subsystem = NULL; + + shadow_subsystem_load_entry_points(&ep); + + if (!ep.New) + return NULL; + + subsystem = ep.New(); + + if (!subsystem) + return NULL; + + CopyMemory(&(subsystem->ep), &ep, sizeof(RDP_SHADOW_ENTRY_POINTS)); + + return subsystem; +} + +void shadow_subsystem_free(rdpShadowSubsystem* subsystem) +{ + if (subsystem && subsystem->ep.Free) + subsystem->ep.Free(subsystem); +} + +int shadow_subsystem_init(rdpShadowSubsystem* subsystem, rdpShadowServer* server) +{ + int status = -1; + + if (!subsystem || !subsystem->ep.Init) + return -1; + + subsystem->server = server; + subsystem->selectedMonitor = server->selectedMonitor; + + if (!(subsystem->MsgPipe = MessagePipe_New())) + goto fail; + + if (!(subsystem->updateEvent = shadow_multiclient_new())) + goto fail; + + if ((status = subsystem->ep.Init(subsystem)) >= 0) + return status; + +fail: + if (subsystem->MsgPipe) + { + MessagePipe_Free(subsystem->MsgPipe); + subsystem->MsgPipe = NULL; + } + + if (subsystem->updateEvent) + { + shadow_multiclient_free(subsystem->updateEvent); + subsystem->updateEvent = NULL; + } + + return status; +} + +static void shadow_subsystem_free_queued_message(void* obj) +{ + wMessage* message = (wMessage*)obj; + if (message->Free) + { + message->Free(message); + message->Free = NULL; + } +} + +void shadow_subsystem_uninit(rdpShadowSubsystem* subsystem) +{ + if (!subsystem) + return; + + if (subsystem->ep.Uninit) + subsystem->ep.Uninit(subsystem); + + if (subsystem->MsgPipe) + { + wObject* obj1 = NULL; + wObject* obj2 = NULL; + /* Release resource in messages before free */ + obj1 = MessageQueue_Object(subsystem->MsgPipe->In); + + obj1->fnObjectFree = shadow_subsystem_free_queued_message; + MessageQueue_Clear(subsystem->MsgPipe->In); + + obj2 = MessageQueue_Object(subsystem->MsgPipe->Out); + obj2->fnObjectFree = shadow_subsystem_free_queued_message; + MessageQueue_Clear(subsystem->MsgPipe->Out); + MessagePipe_Free(subsystem->MsgPipe); + subsystem->MsgPipe = NULL; + } + + if (subsystem->updateEvent) + { + shadow_multiclient_free(subsystem->updateEvent); + subsystem->updateEvent = NULL; + } +} + +int shadow_subsystem_start(rdpShadowSubsystem* subsystem) +{ + int status = 0; + + if (!subsystem || !subsystem->ep.Start) + return -1; + + status = subsystem->ep.Start(subsystem); + + return status; +} + +int shadow_subsystem_stop(rdpShadowSubsystem* subsystem) +{ + int status = 0; + + if (!subsystem || !subsystem->ep.Stop) + return -1; + + status = subsystem->ep.Stop(subsystem); + + return status; +} + +UINT32 shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors) +{ + UINT32 numMonitors = 0; + RDP_SHADOW_ENTRY_POINTS ep; + + if (shadow_subsystem_load_entry_points(&ep) < 0) + return 0; + + numMonitors = ep.EnumMonitors(monitors, maxMonitors); + + return numMonitors; +} + +/** + * Common function for subsystem implementation. + * This function convert 32bit ARGB format pixels to xormask data + * and andmask data and fill into SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE + * Caller should free the andMaskData and xorMaskData later. + */ +int shadow_subsystem_pointer_convert_alpha_pointer_data( + BYTE* pixels, BOOL premultiplied, UINT32 width, UINT32 height, + SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE* pointerColor) +{ + BYTE* pSrc8 = NULL; + BYTE* pDst8 = NULL; + UINT32 xorStep = 0; + UINT32 andStep = 0; + UINT32 andBit = 0; + BYTE* andBits = NULL; + UINT32 andPixel = 0; + BYTE A = 0; + BYTE R = 0; + BYTE G = 0; + BYTE B = 0; + + xorStep = (width * 3); + xorStep += (xorStep % 2); + + andStep = ((width + 7) / 8); + andStep += (andStep % 2); + + pointerColor->lengthXorMask = height * xorStep; + pointerColor->xorMaskData = (BYTE*)calloc(1, pointerColor->lengthXorMask); + + if (!pointerColor->xorMaskData) + return -1; + + pointerColor->lengthAndMask = height * andStep; + pointerColor->andMaskData = (BYTE*)calloc(1, pointerColor->lengthAndMask); + + if (!pointerColor->andMaskData) + { + free(pointerColor->xorMaskData); + pointerColor->xorMaskData = NULL; + return -1; + } + + for (UINT32 y = 0; y < height; y++) + { + pSrc8 = &pixels[(width * 4) * (height - 1 - y)]; + pDst8 = &(pointerColor->xorMaskData[y * xorStep]); + + andBit = 0x80; + andBits = &(pointerColor->andMaskData[andStep * y]); + + for (UINT32 x = 0; x < width; x++) + { + B = *pSrc8++; + G = *pSrc8++; + R = *pSrc8++; + A = *pSrc8++; + + andPixel = 0; + + if (A < 64) + A = 0; /* pixel cannot be partially transparent */ + + if (!A) + { + /* transparent pixel: XOR = black, AND = 1 */ + andPixel = 1; + B = G = R = 0; + } + else + { + if (premultiplied) + { + B = (B * 0xFF) / A; + G = (G * 0xFF) / A; + R = (R * 0xFF) / A; + } + } + + *pDst8++ = B; + *pDst8++ = G; + *pDst8++ = R; + + if (andPixel) + *andBits |= andBit; + if (!(andBit >>= 1)) + { + andBits++; + andBit = 0x80; + } + } + } + + return 1; +} + +void shadow_subsystem_frame_update(rdpShadowSubsystem* subsystem) +{ + shadow_multiclient_publish_and_wait(subsystem->updateEvent); +} diff --git a/server/shadow/shadow_subsystem.h b/server/shadow/shadow_subsystem.h new file mode 100644 index 0000000..206dfdd --- /dev/null +++ b/server/shadow/shadow_subsystem.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_SUBSYSTEM_H +#define FREERDP_SERVER_SHADOW_SUBSYSTEM_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + void shadow_subsystem_free(rdpShadowSubsystem* subsystem); + + WINPR_ATTR_MALLOC(shadow_subsystem_free, 1) + rdpShadowSubsystem* shadow_subsystem_new(void); + + int shadow_subsystem_init(rdpShadowSubsystem* subsystem, rdpShadowServer* server); + void shadow_subsystem_uninit(rdpShadowSubsystem* subsystem); + + int shadow_subsystem_start(rdpShadowSubsystem* subsystem); + int shadow_subsystem_stop(rdpShadowSubsystem* subsystem); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_SUBSYSTEM_H */ diff --git a/server/shadow/shadow_subsystem_builtin.c b/server/shadow/shadow_subsystem_builtin.c new file mode 100644 index 0000000..627458e --- /dev/null +++ b/server/shadow/shadow_subsystem_builtin.c @@ -0,0 +1,75 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2016 Jiang Zihao <zihao.jiang@yahoo.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 <freerdp/config.h> + +#include <freerdp/server/shadow.h> + +typedef struct +{ + const char* (*name)(void); + pfnShadowSubsystemEntry entry; +} RDP_SHADOW_SUBSYSTEM; + +extern int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints); +extern const char* ShadowSubsystemName(void); + +static RDP_SHADOW_SUBSYSTEM g_Subsystems[] = { + + { ShadowSubsystemName, ShadowSubsystemEntry } +}; + +static size_t g_SubsystemCount = ARRAYSIZE(g_Subsystems); + +static pfnShadowSubsystemEntry shadow_subsystem_load_static_entry(const char* name) +{ + if (!name) + { + if (g_SubsystemCount > 0) + { + const RDP_SHADOW_SUBSYSTEM* cur = &g_Subsystems[0]; + WINPR_ASSERT(cur->entry); + + return cur->entry; + } + + return NULL; + } + + for (size_t index = 0; index < g_SubsystemCount; index++) + { + const RDP_SHADOW_SUBSYSTEM* cur = &g_Subsystems[index]; + WINPR_ASSERT(cur->name); + WINPR_ASSERT(cur->entry); + + if (strcmp(name, cur->name()) == 0) + return cur->entry; + } + + return NULL; +} + +void shadow_subsystem_set_entry_builtin(const char* name) +{ + pfnShadowSubsystemEntry entry = shadow_subsystem_load_static_entry(name); + + if (entry) + shadow_subsystem_set_entry(entry); + + return; +} diff --git a/server/shadow/shadow_surface.c b/server/shadow/shadow_surface.c new file mode 100644 index 0000000..16aecce --- /dev/null +++ b/server/shadow/shadow_surface.c @@ -0,0 +1,104 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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 <freerdp/config.h> + +#include "shadow.h" + +#include "shadow_surface.h" +#define ALIGN_SCREEN_SIZE(size, align) \ + ((((size) % (align)) != 0) ? ((size) + (align) - ((size) % (align))) : (size)) + +rdpShadowSurface* shadow_surface_new(rdpShadowServer* server, UINT16 x, UINT16 y, UINT32 width, + UINT32 height) +{ + rdpShadowSurface* surface = NULL; + surface = (rdpShadowSurface*)calloc(1, sizeof(rdpShadowSurface)); + + if (!surface) + return NULL; + + surface->server = server; + surface->x = x; + surface->y = y; + surface->width = width; + surface->height = height; + surface->scanline = ALIGN_SCREEN_SIZE(surface->width, 32) * 4; + surface->format = PIXEL_FORMAT_BGRX32; + surface->data = (BYTE*)calloc(ALIGN_SCREEN_SIZE(surface->height, 32), surface->scanline); + + if (!surface->data) + { + free(surface); + return NULL; + } + + if (!InitializeCriticalSectionAndSpinCount(&(surface->lock), 4000)) + { + free(surface->data); + free(surface); + return NULL; + } + + region16_init(&(surface->invalidRegion)); + return surface; +} + +void shadow_surface_free(rdpShadowSurface* surface) +{ + if (!surface) + return; + + free(surface->data); + DeleteCriticalSection(&(surface->lock)); + region16_uninit(&(surface->invalidRegion)); + free(surface); +} + +BOOL shadow_surface_resize(rdpShadowSurface* surface, UINT16 x, UINT16 y, UINT32 width, + UINT32 height) +{ + BYTE* buffer = NULL; + UINT32 scanline = ALIGN_SCREEN_SIZE(width, 4) * 4; + + if (!surface) + return FALSE; + + if ((width == surface->width) && (height == surface->height)) + { + /* We don't need to reset frame buffer, just update left top */ + surface->x = x; + surface->y = y; + return TRUE; + } + + buffer = (BYTE*)realloc(surface->data, 1ull * scanline * ALIGN_SCREEN_SIZE(height, 4ull)); + + if (buffer) + { + surface->x = x; + surface->y = y; + surface->width = width; + surface->height = height; + surface->scanline = scanline; + surface->data = buffer; + return TRUE; + } + + return FALSE; +} diff --git a/server/shadow/shadow_surface.h b/server/shadow/shadow_surface.h new file mode 100644 index 0000000..277df0a --- /dev/null +++ b/server/shadow/shadow_surface.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.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. + */ + +#ifndef FREERDP_SERVER_SHADOW_SURFACE_H +#define FREERDP_SERVER_SHADOW_SURFACE_H + +#include <freerdp/server/shadow.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + void shadow_surface_free(rdpShadowSurface* surface); + + WINPR_ATTR_MALLOC(shadow_surface_free, 1) + rdpShadowSurface* shadow_surface_new(rdpShadowServer* server, UINT16 x, UINT16 y, UINT32 width, + UINT32 height); + + BOOL shadow_surface_resize(rdpShadowSurface* surface, UINT16 x, UINT16 y, UINT32 width, + UINT32 height); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_SERVER_SHADOW_SURFACE_H */ |