server: proxy: implement session capture plugin

This commit is contained in:
Kobi Mizrachi 2020-06-01 09:37:53 +03:00 committed by akallabeth
parent 920acd4c0e
commit 19809bf338
6 changed files with 538 additions and 0 deletions

View File

@ -0,0 +1,33 @@
#
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP Proxy Server Capture Module
#
# 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.
#
set(PLUGIN_NAME "proxy-capture-plugin")
add_library(${PLUGIN_NAME} MODULE
cap_main.c
cap_config.c
cap_config.h
cap_protocol.c
cap_protocol.h
)
set_target_properties(${PLUGIN_NAME} PROPERTIES PREFIX "")
set_target_properties(${PLUGIN_NAME} PROPERTIES NO_SONAME 1)
set_target_properties(${PLUGIN_NAME} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${FREERDP_PROXY_PLUGINDIR}")

View File

@ -0,0 +1,97 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server Session Capture Module
*
* 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.
*/
#include <winpr/environment.h>
#include <freerdp/types.h>
#include <errno.h>
#include "cap_config.h"
BOOL capture_plugin_init_config(captureConfig* config)
{
const char* name = "PROXY_CAPTURE_TARGET";
char* tmp = NULL;
DWORD nSize = GetEnvironmentVariableA(name, NULL, 0);
if (nSize)
{
char* colon;
int addrLen;
unsigned long port;
tmp = (LPSTR)malloc(nSize);
if (!tmp)
return FALSE;
if (GetEnvironmentVariableA(name, tmp, nSize) != nSize - 1)
{
free(tmp);
return FALSE;
}
colon = strchr(tmp, ':');
if (!colon)
{
free(tmp);
return FALSE;
}
addrLen = (int)(colon - tmp);
config->host = malloc(addrLen + 1);
if (!config->host)
{
free(tmp);
return FALSE;
}
strncpy(config->host, tmp, addrLen);
config->host[addrLen] = '\0';
port = strtoul(colon + 1, NULL, 0);
if ((errno != 0) || (port > UINT16_MAX))
{
free(config->host);
config->host = NULL;
free(tmp);
return FALSE;
}
config->port = port;
free(tmp);
}
else
{
config->host = _strdup("127.0.0.1");
if (!config->host)
return FALSE;
config->port = 8889;
}
return TRUE;
}
void capture_plugin_config_free_internal(captureConfig* config)
{
free(config->host);
config->host = NULL;
}

View File

@ -0,0 +1,29 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server Session Capture Module
*
* 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.
*/
#include <freerdp/types.h>
typedef struct capture_config
{
UINT16 port;
char* host;
} captureConfig;
BOOL capture_plugin_init_config(captureConfig* config);
void capture_plugin_config_free_internal(captureConfig* config);

View File

@ -0,0 +1,287 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server Session Capture Module
*
* 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.
*/
#define TAG MODULE_TAG("capture")
#define PLUGIN_NAME "capture"
#define PLUGIN_DESC "stream egfx connections over tcp"
#include <errno.h>
#include <winpr/image.h>
#include <freerdp/gdi/gdi.h>
#include <winpr/winsock.h>
#include "pf_log.h"
#include "modules_api.h"
#include "pf_context.h"
#include "cap_config.h"
#include "cap_protocol.h"
#define BUFSIZE 8092
static proxyPluginsManager* g_plugins_manager = NULL;
static captureConfig config = { 0 };
static SOCKET capture_plugin_init_socket()
{
int status;
int sockfd;
struct sockaddr_in addr = { 0 };
sockfd = _socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd == -1)
return -1;
addr.sin_family = AF_INET;
addr.sin_port = htons(config.port);
inet_pton(AF_INET, config.host, &(addr.sin_addr));
status = _connect(sockfd, (const struct sockaddr*)&addr, sizeof(addr));
if (status < 0)
{
close(sockfd);
return -1;
}
return sockfd;
}
static BOOL capture_plugin_send_data(SOCKET sockfd, const BYTE* buffer, size_t len)
{
size_t chunk_len;
int nsent;
if (!buffer)
return FALSE;
while (len > 0)
{
chunk_len = len > BUFSIZE ? BUFSIZE : len;
nsent = _send(sockfd, (const char*)buffer, chunk_len, 0);
if (nsent == -1)
return FALSE;
buffer += nsent;
len -= nsent;
}
return TRUE;
}
static BOOL capture_plugin_send_packet(SOCKET sockfd, wStream* packet)
{
size_t len;
BYTE* buffer;
BOOL result = FALSE;
if (!packet)
return FALSE;
buffer = Stream_Buffer(packet);
len = Stream_Capacity(packet);
if (!capture_plugin_send_data(sockfd, buffer, len))
{
WLog_ERR(TAG, "error while transmitting frame: errno=%d", errno);
goto error;
}
result = TRUE;
error:
Stream_Free(packet, TRUE);
return result;
}
static SOCKET capture_plugin_get_socket(proxyData* pdata)
{
void* custom;
custom = g_plugins_manager->GetPluginData(PLUGIN_NAME, pdata);
if (!custom)
return -1;
return (SOCKET)custom;
}
static BOOL capture_plugin_session_end(proxyData* pdata)
{
SOCKET socket;
BOOL ret;
socket = capture_plugin_get_socket(pdata);
if (socket == -1)
return FALSE;
wStream* s = capture_plugin_packet_new(SESSION_END_PDU_BASE_SIZE, MESSAGE_TYPE_SESSION_END);
if (!s)
return FALSE;
ret = capture_plugin_send_packet(socket, s);
closesocket(socket);
return ret;
}
static BOOL capture_plugin_send_frame(pClientContext* pc, SOCKET socket, const BYTE* buffer)
{
size_t frame_size;
BOOL ret = FALSE;
wStream* s = NULL;
BYTE* bmp_header = NULL;
rdpSettings* settings = pc->context.settings;
frame_size = settings->DesktopWidth * settings->DesktopHeight * (settings->ColorDepth / 8);
bmp_header = winpr_bitmap_construct_header(settings->DesktopWidth, settings->DesktopHeight,
settings->ColorDepth);
if (!bmp_header)
return FALSE;
/*
* capture frame packet indicates a packet that contains a frame buffer. payload length is
* marked as 0, and receiving side must read `frame_size` bytes, a constant size of
* width*height*(bpp/8) from the socket, to receive the full frame buffer.
*/
s = capture_plugin_packet_new(0, MESSAGE_TYPE_CAPTURED_FRAME);
if (!s)
goto error;
if (!capture_plugin_send_packet(socket, s))
goto error;
ret = capture_plugin_send_data(socket, bmp_header, WINPR_IMAGE_BMP_HEADER_LEN);
if (!ret)
goto error;
ret = capture_plugin_send_data(socket, buffer, frame_size);
error:
free(bmp_header);
return ret;
}
static BOOL capture_plugin_client_end_paint(proxyData* pdata)
{
pClientContext* pc = pdata->pc;
rdpGdi* gdi = pc->context.gdi;
SOCKET socket;
if (gdi->suppressOutput)
return TRUE;
if (gdi->primary->hdc->hwnd->ninvalid < 1)
return TRUE;
socket = capture_plugin_get_socket(pdata);
if (socket == -1)
return FALSE;
if (!capture_plugin_send_frame(pc, socket, gdi->primary_buffer))
{
WLog_ERR(TAG, "capture_plugin_send_frame failed!");
return FALSE;
}
gdi->primary->hdc->hwnd->invalid->null = TRUE;
gdi->primary->hdc->hwnd->ninvalid = 0;
return TRUE;
}
static BOOL capture_plugin_client_post_connect(proxyData* pdata)
{
SOCKET socket;
wStream* s;
pClientContext* pc = pdata->pc;
rdpSettings* settings = pc->context.settings;
socket = capture_plugin_init_socket();
if (socket == -1)
{
WLog_ERR(TAG, "failed to establish a connection");
return FALSE;
}
g_plugins_manager->SetPluginData(PLUGIN_NAME, pdata, (void*)socket);
s = capture_plugin_create_session_info_packet(settings);
if (!s)
return FALSE;
return capture_plugin_send_packet(socket, s);
}
static BOOL capture_plugin_server_post_connect(proxyData* pdata)
{
pServerContext* ps = pdata->ps;
proxyConfig* config = pdata->config;
rdpSettings* settings = ps->context.settings;
if (!config->GFX || !config->SessionCapture)
{
WLog_ERR(TAG, "config options 'GFX' and 'SessionCapture' options must be set to true!");
return FALSE;
}
if (!settings->SupportGraphicsPipeline)
{
WLog_ERR(TAG, "session capture is only supported for GFX clients, denying connection");
return FALSE;
}
return TRUE;
}
static BOOL capture_plugin_unload()
{
capture_plugin_config_free_internal(&config);
return TRUE;
}
static proxyPlugin demo_plugin = {
PLUGIN_NAME, /* name */
PLUGIN_DESC, /* description */
capture_plugin_unload, /* PluginUnload */
NULL, /* ClientPreConnect */
capture_plugin_client_post_connect, /* ClientPostConnect */
NULL, /* ClientLoginFailure */
capture_plugin_client_end_paint, /* ClientEndPaint */
capture_plugin_server_post_connect, /* ServerPostConnect */
NULL, /* ServerChannelsInit */
NULL, /* ServerChannelsFree */
capture_plugin_session_end, /* Session End */
NULL, /* KeyboardEvent */
NULL, /* MouseEvent */
NULL, /* ClientChannelData */
NULL, /* ServerChannelData */
};
BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager)
{
g_plugins_manager = plugins_manager;
if (!capture_plugin_init_config(&config))
{
WLog_ERR(TAG, "failed to load config");
return FALSE;
}
WLog_INFO(TAG, "host: %s, port: %" PRIu16 "", config.host, config.port);
return plugins_manager->RegisterPlugin(&demo_plugin);
}

View File

@ -0,0 +1,57 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server Session Capture Module
*
* 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.
*/
#include "cap_protocol.h"
wStream* capture_plugin_packet_new(UINT32 payload_size, UINT16 type)
{
wStream* stream = Stream_New(NULL, HEADER_SIZE + payload_size);
if (!stream)
return NULL;
Stream_Write_UINT32(stream, payload_size);
Stream_Write_UINT16(stream, type);
return stream;
}
wStream* capture_plugin_create_session_info_packet(rdpSettings* settings)
{
UINT16 username_length;
wStream* s = NULL;
if (!settings || !settings->Username)
return NULL;
username_length = strlen(settings->Username);
if (username_length == 0)
return NULL;
s = capture_plugin_packet_new(SESSION_INFO_PDU_BASE_SIZE + username_length,
MESSAGE_TYPE_SESSION_INFO);
if (!s)
return NULL;
Stream_Write_UINT16(s, username_length); /* username length (2 bytes) */
Stream_Write(s, settings->Username, username_length); /* username */
Stream_Write_UINT32(s, settings->DesktopWidth); /* desktop width (4 bytes) */
Stream_Write_UINT32(s, settings->DesktopHeight); /* desktop height (4 bytes) */
Stream_Write_UINT32(s, settings->ColorDepth); /* color depth (4 bytes) */
return s;
}

View File

@ -0,0 +1,35 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* FreeRDP Proxy Server Session Capture Module
*
* 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.
*/
#include <winpr/stream.h>
#include <freerdp/settings.h>
/* protocol message sizes */
#define HEADER_SIZE 6
#define SESSION_INFO_PDU_BASE_SIZE 14
#define SESSION_END_PDU_BASE_SIZE 0
#define CAPTURED_FRAME_PDU_BASE_SIZE 0
/* protocol message types */
#define MESSAGE_TYPE_SESSION_INFO 1
#define MESSAGE_TYPE_CAPTURED_FRAME 2
#define MESSAGE_TYPE_SESSION_END 3
wStream* capture_plugin_packet_new(UINT32 payload_size, UINT16 type);
wStream* capture_plugin_create_session_info_packet(rdpSettings* settings);