mirror of
https://github.com/morgan9e/FreeRDP
synced 2026-04-15 00:44:19 +09:00
[gateway,websocket] implement plain websocket transport
* factor out most websocket specific code parts into websocket.c * create wst.c (Websocket Transport) as gateway transport implementation * introduce GatewayUrl setting that holds the websocket url * introduce GatewayHttpExtAuthBearer that holds the HTTP Bearer * GatewayHttpExtAuthBearer can be used by both rdg and wst
This commit is contained in:
@@ -2540,7 +2540,7 @@ static BOOL parse_gateway_options(rdpSettings* settings, const COMMAND_LINE_ARGU
|
||||
validOption = TRUE;
|
||||
allowHttpOpts = FALSE;
|
||||
}
|
||||
|
||||
|
||||
const char* bearer = option_starts_with("bearer:", argval);
|
||||
if (bearer)
|
||||
{
|
||||
@@ -2550,6 +2550,17 @@ static BOOL parse_gateway_options(rdpSettings* settings, const COMMAND_LINE_ARGU
|
||||
allowHttpOpts = FALSE;
|
||||
}
|
||||
|
||||
const char* gwurl = option_starts_with("url:", argval);
|
||||
if (gwurl)
|
||||
{
|
||||
if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurl))
|
||||
goto fail;
|
||||
if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT))
|
||||
goto fail;
|
||||
validOption = TRUE;
|
||||
allowHttpOpts = FALSE;
|
||||
}
|
||||
|
||||
const char* um = option_starts_with("usage-method:", argval);
|
||||
if (um)
|
||||
{
|
||||
|
||||
@@ -177,7 +177,7 @@ static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = {
|
||||
"g:<gateway>[:<port>],u:<user>,d:<domain>,p:<password>,usage-method:["
|
||||
"direct|detect],access-token:<"
|
||||
"token>,type:[rpc|http[,no-websockets][,extauth-sspi-ntlm]|auto[,no-websockets][,extauth-"
|
||||
"sspi-ntlm]],",
|
||||
"sspi-ntlm]],url:<wss://url>,bearer:<oauth2-bearer-token>",
|
||||
NULL, NULL, -1, "gw", "Gateway Hostname" },
|
||||
#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
|
||||
{ "g", COMMAND_LINE_VALUE_REQUIRED, "<gateway>[:<port>]", NULL, NULL, -1, NULL,
|
||||
|
||||
@@ -767,6 +767,7 @@ extern "C"
|
||||
#define FreeRDP_GatewayHttpUseWebsockets (2000)
|
||||
#define FreeRDP_GatewayHttpExtAuthSspiNtlm (2001)
|
||||
#define FreeRDP_GatewayHttpExtAuthBearer (2002)
|
||||
#define FreeRDP_GatewayUrl (2003)
|
||||
#define FreeRDP_ProxyType (2015)
|
||||
#define FreeRDP_ProxyHostname (2016)
|
||||
#define FreeRDP_ProxyPort (2017)
|
||||
@@ -1342,7 +1343,8 @@ extern "C"
|
||||
ALIGN64 BOOL GatewayHttpUseWebsockets; /* 2000 */
|
||||
ALIGN64 BOOL GatewayHttpExtAuthSspiNtlm; /* 2001 */
|
||||
ALIGN64 char* GatewayHttpExtAuthBearer; /* 2002 */
|
||||
UINT64 padding2015[2015 - 2003]; /* 2003 */
|
||||
ALIGN64 char* GatewayUrl; /* 2003 */
|
||||
UINT64 padding2015[2015 - 2004]; /* 2004 */
|
||||
|
||||
/* Proxy */
|
||||
ALIGN64 UINT32 ProxyType; /* 2015 */
|
||||
|
||||
@@ -2636,15 +2636,18 @@ const char* freerdp_settings_get_string(const rdpSettings* settings, size_t id)
|
||||
case FreeRDP_GatewayHostname:
|
||||
return settings->GatewayHostname;
|
||||
|
||||
case FreeRDP_GatewayHttpExtAuthBearer:
|
||||
return settings->GatewayHttpExtAuthBearer;
|
||||
|
||||
case FreeRDP_GatewayPassword:
|
||||
return settings->GatewayPassword;
|
||||
|
||||
case FreeRDP_GatewayUrl:
|
||||
return settings->GatewayUrl;
|
||||
|
||||
case FreeRDP_GatewayUsername:
|
||||
return settings->GatewayUsername;
|
||||
|
||||
case FreeRDP_GatewayHttpExtAuthBearer:
|
||||
return settings->GatewayHttpExtAuthBearer;
|
||||
|
||||
case FreeRDP_HomePath:
|
||||
return settings->HomePath;
|
||||
|
||||
@@ -2908,15 +2911,18 @@ char* freerdp_settings_get_string_writable(rdpSettings* settings, size_t id)
|
||||
case FreeRDP_GatewayHostname:
|
||||
return settings->GatewayHostname;
|
||||
|
||||
case FreeRDP_GatewayHttpExtAuthBearer:
|
||||
return settings->GatewayHttpExtAuthBearer;
|
||||
|
||||
case FreeRDP_GatewayPassword:
|
||||
return settings->GatewayPassword;
|
||||
|
||||
case FreeRDP_GatewayUrl:
|
||||
return settings->GatewayUrl;
|
||||
|
||||
case FreeRDP_GatewayUsername:
|
||||
return settings->GatewayUsername;
|
||||
|
||||
case FreeRDP_GatewayHttpExtAuthBearer:
|
||||
return settings->GatewayHttpExtAuthBearer;
|
||||
|
||||
case FreeRDP_HomePath:
|
||||
return settings->HomePath;
|
||||
|
||||
@@ -3189,15 +3195,18 @@ BOOL freerdp_settings_set_string_(rdpSettings* settings, size_t id, char* val, s
|
||||
case FreeRDP_GatewayHostname:
|
||||
return update_string_(&settings->GatewayHostname, cnv.c, len);
|
||||
|
||||
case FreeRDP_GatewayHttpExtAuthBearer:
|
||||
return update_string_(&settings->GatewayHttpExtAuthBearer, cnv.c, len);
|
||||
|
||||
case FreeRDP_GatewayPassword:
|
||||
return update_string_(&settings->GatewayPassword, cnv.c, len);
|
||||
|
||||
case FreeRDP_GatewayUrl:
|
||||
return update_string_(&settings->GatewayUrl, cnv.c, len);
|
||||
|
||||
case FreeRDP_GatewayUsername:
|
||||
return update_string_(&settings->GatewayUsername, cnv.c, len);
|
||||
|
||||
case FreeRDP_GatewayHttpExtAuthBearer:
|
||||
return update_string_(&settings->GatewayHttpExtAuthBearer, cnv.c, len);
|
||||
|
||||
case FreeRDP_HomePath:
|
||||
return update_string_(&settings->HomePath, cnv.c, len);
|
||||
|
||||
@@ -3486,15 +3495,18 @@ BOOL freerdp_settings_set_string_copy_(rdpSettings* settings, size_t id, const c
|
||||
case FreeRDP_GatewayHostname:
|
||||
return update_string_copy_(&settings->GatewayHostname, cnv.cc, len, cleanup);
|
||||
|
||||
case FreeRDP_GatewayHttpExtAuthBearer:
|
||||
return update_string_copy_(&settings->GatewayHttpExtAuthBearer, cnv.cc, len, cleanup);
|
||||
|
||||
case FreeRDP_GatewayPassword:
|
||||
return update_string_copy_(&settings->GatewayPassword, cnv.cc, len, cleanup);
|
||||
|
||||
case FreeRDP_GatewayUrl:
|
||||
return update_string_copy_(&settings->GatewayUrl, cnv.cc, len, cleanup);
|
||||
|
||||
case FreeRDP_GatewayUsername:
|
||||
return update_string_copy_(&settings->GatewayUsername, cnv.cc, len, cleanup);
|
||||
|
||||
case FreeRDP_GatewayHttpExtAuthBearer:
|
||||
return update_string_copy_(&settings->GatewayHttpExtAuthBearer, cnv.cc, len, cleanup);
|
||||
|
||||
case FreeRDP_HomePath:
|
||||
return update_string_copy_(&settings->HomePath, cnv.cc, len, cleanup);
|
||||
|
||||
|
||||
@@ -106,8 +106,6 @@ static const struct settings_str_entry settings_map[] = {
|
||||
{ FreeRDP_GatewayEnabled, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GatewayEnabled" },
|
||||
{ FreeRDP_GatewayHttpExtAuthSspiNtlm, FREERDP_SETTINGS_TYPE_BOOL,
|
||||
"FreeRDP_GatewayHttpExtAuthSspiNtlm" },
|
||||
{ FreeRDP_GatewayHttpExtAuthBearer, FREERDP_SETTINGS_TYPE_STRING,
|
||||
"FreeRDP_GatewayHttpExtAuthBearer" },
|
||||
{ FreeRDP_GatewayHttpTransport, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_GatewayHttpTransport" },
|
||||
{ FreeRDP_GatewayHttpUseWebsockets, FREERDP_SETTINGS_TYPE_BOOL,
|
||||
"FreeRDP_GatewayHttpUseWebsockets" },
|
||||
@@ -460,7 +458,10 @@ static const struct settings_str_entry settings_map[] = {
|
||||
{ FreeRDP_GatewayAccessToken, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayAccessToken" },
|
||||
{ FreeRDP_GatewayDomain, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayDomain" },
|
||||
{ FreeRDP_GatewayHostname, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayHostname" },
|
||||
{ FreeRDP_GatewayHttpExtAuthBearer, FREERDP_SETTINGS_TYPE_STRING,
|
||||
"FreeRDP_GatewayHttpExtAuthBearer" },
|
||||
{ FreeRDP_GatewayPassword, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayPassword" },
|
||||
{ FreeRDP_GatewayUrl, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayUrl" },
|
||||
{ FreeRDP_GatewayUsername, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_GatewayUsername" },
|
||||
{ FreeRDP_HomePath, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_HomePath" },
|
||||
{ FreeRDP_ImeFileName, FREERDP_SETTINGS_TYPE_STRING, "FreeRDP_ImeFileName" },
|
||||
|
||||
@@ -43,6 +43,10 @@ set(${MODULE_PREFIX}_GATEWAY_SRCS
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/rts_signature.h
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/http.c
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/http.h
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/websocket.c
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/websocket.h
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/wst.c
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/wst.h
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/ncacn_http.c
|
||||
${${MODULE_PREFIX}_GATEWAY_DIR}/ncacn_http.h)
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <freerdp/utils/smartcardlogon.h>
|
||||
|
||||
#include "rdg.h"
|
||||
#include "websocket.h"
|
||||
#include "../credssp_auth.h"
|
||||
#include "../proxy.h"
|
||||
#include "../rdp.h"
|
||||
@@ -110,42 +111,6 @@
|
||||
#define HTTP_CAPABILITY_REAUTH 0x10
|
||||
#define HTTP_CAPABILITY_UDP_TRANSPORT 0x20
|
||||
|
||||
#define WEBSOCKET_MASK_BIT 0x80
|
||||
#define WEBSOCKET_FIN_BIT 0x80
|
||||
|
||||
typedef enum
|
||||
{
|
||||
WebsocketContinuationOpcode = 0x0,
|
||||
WebsocketTextOpcode = 0x1,
|
||||
WebsocketBinaryOpcode = 0x2,
|
||||
WebsocketCloseOpcode = 0x8,
|
||||
WebsocketPingOpcode = 0x9,
|
||||
WebsocketPongOpcode = 0xa,
|
||||
} WEBSOCKET_OPCODE;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
WebsocketStateOpcodeAndFin,
|
||||
WebsocketStateLengthAndMasking,
|
||||
WebsocketStateShortLength,
|
||||
WebsocketStateLongLength,
|
||||
WebSocketStateMaskingKey,
|
||||
WebSocketStatePayload,
|
||||
} WEBSOCKET_STATE;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
size_t payloadLength;
|
||||
uint32_t maskingKey;
|
||||
BOOL masking;
|
||||
BOOL closeSent;
|
||||
BYTE opcode;
|
||||
BYTE fragmentOriginalOpcode;
|
||||
BYTE lengthAndMaskPosition;
|
||||
WEBSOCKET_STATE state;
|
||||
wStream* responseStreamBuffer;
|
||||
} rdg_http_websocket_context;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ChunkStateLenghHeader,
|
||||
@@ -168,7 +133,7 @@ typedef struct
|
||||
union _context
|
||||
{
|
||||
rdg_http_encoding_chunked_context chunked;
|
||||
rdg_http_websocket_context websocket;
|
||||
websocket_context websocket;
|
||||
} context;
|
||||
} rdg_http_encoding_context;
|
||||
|
||||
@@ -378,424 +343,18 @@ static BOOL rdg_write_chunked(BIO* bio, wStream* sPacket)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL rdg_write_websocket(BIO* bio, wStream* sPacket, WEBSOCKET_OPCODE opcode)
|
||||
{
|
||||
size_t len;
|
||||
size_t fullLen;
|
||||
int status;
|
||||
wStream* sWS;
|
||||
|
||||
uint32_t maskingKey;
|
||||
|
||||
size_t streamPos;
|
||||
|
||||
len = Stream_Length(sPacket);
|
||||
Stream_SetPosition(sPacket, 0);
|
||||
|
||||
if (len > INT_MAX)
|
||||
return FALSE;
|
||||
|
||||
if (len < 126)
|
||||
fullLen = len + 6; /* 2 byte "mini header" + 4 byte masking key */
|
||||
else if (len < 0x10000)
|
||||
fullLen = len + 8; /* 2 byte "mini header" + 2 byte length + 4 byte masking key */
|
||||
else
|
||||
fullLen = len + 14; /* 2 byte "mini header" + 8 byte length + 4 byte masking key */
|
||||
|
||||
sWS = Stream_New(NULL, fullLen);
|
||||
if (!sWS)
|
||||
return FALSE;
|
||||
|
||||
winpr_RAND(&maskingKey, 4);
|
||||
|
||||
Stream_Write_UINT8(sWS, WEBSOCKET_FIN_BIT | opcode);
|
||||
if (len < 126)
|
||||
Stream_Write_UINT8(sWS, len | WEBSOCKET_MASK_BIT);
|
||||
else if (len < 0x10000)
|
||||
{
|
||||
Stream_Write_UINT8(sWS, 126 | WEBSOCKET_MASK_BIT);
|
||||
Stream_Write_UINT16_BE(sWS, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream_Write_UINT8(sWS, 127 | WEBSOCKET_MASK_BIT);
|
||||
Stream_Write_UINT32_BE(sWS, 0); /* payload is limited to INT_MAX */
|
||||
Stream_Write_UINT32_BE(sWS, len);
|
||||
}
|
||||
Stream_Write_UINT32(sWS, maskingKey);
|
||||
|
||||
/* mask as much as possible with 32bit access */
|
||||
for (streamPos = 0; streamPos + 4 <= len; streamPos += 4)
|
||||
{
|
||||
uint32_t data;
|
||||
Stream_Read_UINT32(sPacket, data);
|
||||
Stream_Write_UINT32(sWS, data ^ maskingKey);
|
||||
}
|
||||
|
||||
/* mask the rest byte by byte */
|
||||
for (; streamPos < len; streamPos++)
|
||||
{
|
||||
BYTE data;
|
||||
BYTE* partialMask = ((BYTE*)&maskingKey) + (streamPos % 4);
|
||||
Stream_Read_UINT8(sPacket, data);
|
||||
Stream_Write_UINT8(sWS, data ^ *partialMask);
|
||||
}
|
||||
|
||||
Stream_SealLength(sWS);
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_write(bio, Stream_Buffer(sWS), Stream_Length(sWS));
|
||||
Stream_Free(sWS, TRUE);
|
||||
|
||||
if (status != (SSIZE_T)fullLen)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL rdg_write_packet(rdpRdg* rdg, wStream* sPacket)
|
||||
{
|
||||
if (rdg->transferEncoding.isWebsocketTransport)
|
||||
{
|
||||
if (rdg->transferEncoding.context.websocket.closeSent)
|
||||
return FALSE;
|
||||
return rdg_write_websocket(rdg->tlsOut->bio, sPacket, WebsocketBinaryOpcode);
|
||||
return websocket_write_wstream(rdg->tlsOut->bio, sPacket, WebsocketBinaryOpcode);
|
||||
}
|
||||
|
||||
return rdg_write_chunked(rdg->tlsIn->bio, sPacket);
|
||||
}
|
||||
|
||||
static int rdg_websocket_read_data(BIO* bio, BYTE* pBuffer, size_t size,
|
||||
rdg_http_websocket_context* encodingContext)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
status =
|
||||
BIO_read(bio, pBuffer,
|
||||
(encodingContext->payloadLength < size ? encodingContext->payloadLength : size));
|
||||
if (status <= 0)
|
||||
return status;
|
||||
|
||||
encodingContext->payloadLength -= status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int rdg_websocket_read_discard(BIO* bio, rdg_http_websocket_context* encodingContext)
|
||||
{
|
||||
char _dummy[256];
|
||||
int status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, _dummy, sizeof(_dummy));
|
||||
if (status <= 0)
|
||||
return status;
|
||||
|
||||
encodingContext->payloadLength -= status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int rdg_websocket_read_wstream(BIO* bio, wStream* s,
|
||||
rdg_http_websocket_context* encodingContext)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
return 0;
|
||||
}
|
||||
if (s == NULL)
|
||||
{
|
||||
WLog_WARN(TAG, "wStream* s=%p", s);
|
||||
return -1;
|
||||
}
|
||||
if (Stream_GetRemainingCapacity(s) != encodingContext->payloadLength)
|
||||
{
|
||||
WLog_WARN(TAG,
|
||||
"wStream::capacity [%" PRIuz "] != encodingContext::paylaodLangth [%" PRIuz "]",
|
||||
Stream_GetRemainingCapacity(s), encodingContext->payloadLength);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, Stream_Pointer(s), encodingContext->payloadLength);
|
||||
if (status <= 0)
|
||||
return status;
|
||||
|
||||
Stream_Seek(s, status);
|
||||
|
||||
encodingContext->payloadLength -= status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
Stream_SealLength(s);
|
||||
Stream_SetPosition(s, 0);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static BOOL rdg_websocket_reply_close(BIO* bio, wStream* s)
|
||||
{
|
||||
/* write back close */
|
||||
wStream* closeFrame;
|
||||
uint16_t maskingKey1;
|
||||
uint16_t maskingKey2;
|
||||
int status;
|
||||
size_t closeDataLen;
|
||||
|
||||
closeDataLen = 0;
|
||||
if (s != NULL && Stream_Length(s) >= 2)
|
||||
closeDataLen = 2;
|
||||
|
||||
closeFrame = Stream_New(NULL, 6 + closeDataLen);
|
||||
if (!closeFrame)
|
||||
return FALSE;
|
||||
|
||||
Stream_Write_UINT8(closeFrame, WEBSOCKET_FIN_BIT | WebsocketPongOpcode);
|
||||
Stream_Write_UINT8(closeFrame, closeDataLen | WEBSOCKET_MASK_BIT); /* no payload */
|
||||
winpr_RAND(&maskingKey1, 2);
|
||||
winpr_RAND(&maskingKey2, 2);
|
||||
Stream_Write_UINT16(closeFrame, maskingKey1);
|
||||
Stream_Write_UINT16(closeFrame, maskingKey2); /* unused half, max 2 bytes of data */
|
||||
|
||||
if (closeDataLen == 2)
|
||||
{
|
||||
uint16_t data;
|
||||
Stream_Read_UINT16(s, data);
|
||||
Stream_Write_UINT16(closeFrame, data ^ maskingKey1);
|
||||
}
|
||||
Stream_SealLength(closeFrame);
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_write(bio, Stream_Buffer(closeFrame), Stream_Length(closeFrame));
|
||||
Stream_Free(closeFrame, TRUE);
|
||||
|
||||
/* server MUST close socket now. The server is not allowed anymore to
|
||||
* send frames but if he does, nothing bad would happen */
|
||||
if (status < 0)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL rdg_websocket_reply_pong(BIO* bio, wStream* s)
|
||||
{
|
||||
wStream* closeFrame;
|
||||
uint32_t maskingKey;
|
||||
int status;
|
||||
|
||||
if (s != NULL)
|
||||
return rdg_write_websocket(bio, s, WebsocketPongOpcode);
|
||||
|
||||
closeFrame = Stream_New(NULL, 6);
|
||||
if (!closeFrame)
|
||||
return FALSE;
|
||||
|
||||
Stream_Write_UINT8(closeFrame, WEBSOCKET_FIN_BIT | WebsocketPongOpcode);
|
||||
Stream_Write_UINT8(closeFrame, 0 | WEBSOCKET_MASK_BIT); /* no payload */
|
||||
winpr_RAND(&maskingKey, 4);
|
||||
Stream_Write_UINT32(closeFrame, maskingKey); /* dummy masking key. */
|
||||
Stream_SealLength(closeFrame);
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_write(bio, Stream_Buffer(closeFrame), Stream_Length(closeFrame));
|
||||
Stream_Free(closeFrame, TRUE);
|
||||
|
||||
if (status < 0)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static int rdg_websocket_handle_payload(BIO* bio, BYTE* pBuffer, size_t size,
|
||||
rdg_http_websocket_context* encodingContext)
|
||||
{
|
||||
int status;
|
||||
BYTE effectiveOpcode = ((encodingContext->opcode & 0xf) == WebsocketContinuationOpcode
|
||||
? encodingContext->fragmentOriginalOpcode & 0xf
|
||||
: encodingContext->opcode & 0xf);
|
||||
|
||||
switch (effectiveOpcode)
|
||||
{
|
||||
case WebsocketBinaryOpcode:
|
||||
{
|
||||
status = rdg_websocket_read_data(bio, pBuffer, size, encodingContext);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
return status;
|
||||
}
|
||||
case WebsocketPingOpcode:
|
||||
{
|
||||
if (encodingContext->responseStreamBuffer == NULL)
|
||||
encodingContext->responseStreamBuffer =
|
||||
Stream_New(NULL, encodingContext->payloadLength);
|
||||
|
||||
status = rdg_websocket_read_wstream(bio, encodingContext->responseStreamBuffer,
|
||||
encodingContext);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
if (!encodingContext->closeSent)
|
||||
rdg_websocket_reply_pong(bio, encodingContext->responseStreamBuffer);
|
||||
|
||||
if (encodingContext->responseStreamBuffer)
|
||||
Stream_Free(encodingContext->responseStreamBuffer, TRUE);
|
||||
encodingContext->responseStreamBuffer = NULL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WebsocketCloseOpcode:
|
||||
{
|
||||
if (encodingContext->responseStreamBuffer == NULL)
|
||||
encodingContext->responseStreamBuffer =
|
||||
Stream_New(NULL, encodingContext->payloadLength);
|
||||
|
||||
status = rdg_websocket_read_wstream(bio, encodingContext->responseStreamBuffer,
|
||||
encodingContext);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
rdg_websocket_reply_close(bio, encodingContext->responseStreamBuffer);
|
||||
encodingContext->closeSent = TRUE;
|
||||
|
||||
if (encodingContext->responseStreamBuffer)
|
||||
Stream_Free(encodingContext->responseStreamBuffer, TRUE);
|
||||
encodingContext->responseStreamBuffer = NULL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
WLog_WARN(TAG, "Unimplemented websocket opcode %x. Dropping", effectiveOpcode & 0xf);
|
||||
|
||||
status = rdg_websocket_read_discard(bio, encodingContext);
|
||||
if (status < 0)
|
||||
return status;
|
||||
}
|
||||
/* return how many bytes have been written to pBuffer.
|
||||
* Only WebsocketBinaryOpcode writes into it and it returns directly */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rdg_websocket_read(BIO* bio, BYTE* pBuffer, size_t size,
|
||||
rdg_http_websocket_context* encodingContext)
|
||||
{
|
||||
int status;
|
||||
int effectiveDataLen = 0;
|
||||
WINPR_ASSERT(encodingContext != NULL);
|
||||
while (TRUE)
|
||||
{
|
||||
switch (encodingContext->state)
|
||||
{
|
||||
case WebsocketStateOpcodeAndFin:
|
||||
{
|
||||
BYTE buffer[1];
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, (char*)buffer, 1);
|
||||
if (status <= 0)
|
||||
return (effectiveDataLen > 0 ? effectiveDataLen : status);
|
||||
|
||||
encodingContext->opcode = buffer[0];
|
||||
if (((encodingContext->opcode & 0xf) != WebsocketContinuationOpcode) &&
|
||||
(encodingContext->opcode & 0xf) < 0x08)
|
||||
encodingContext->fragmentOriginalOpcode = encodingContext->opcode;
|
||||
encodingContext->state = WebsocketStateLengthAndMasking;
|
||||
}
|
||||
break;
|
||||
case WebsocketStateLengthAndMasking:
|
||||
{
|
||||
BYTE buffer[1];
|
||||
BYTE len;
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, (char*)buffer, 1);
|
||||
if (status <= 0)
|
||||
return (effectiveDataLen > 0 ? effectiveDataLen : status);
|
||||
|
||||
encodingContext->masking = ((buffer[0] & WEBSOCKET_MASK_BIT) == WEBSOCKET_MASK_BIT);
|
||||
encodingContext->lengthAndMaskPosition = 0;
|
||||
encodingContext->payloadLength = 0;
|
||||
len = buffer[0] & 0x7f;
|
||||
if (len < 126)
|
||||
{
|
||||
encodingContext->payloadLength = len;
|
||||
encodingContext->state = (encodingContext->masking ? WebSocketStateMaskingKey
|
||||
: WebSocketStatePayload);
|
||||
}
|
||||
else if (len == 126)
|
||||
encodingContext->state = WebsocketStateShortLength;
|
||||
else
|
||||
encodingContext->state = WebsocketStateLongLength;
|
||||
}
|
||||
break;
|
||||
case WebsocketStateShortLength:
|
||||
case WebsocketStateLongLength:
|
||||
{
|
||||
BYTE buffer[1];
|
||||
BYTE lenLength = (encodingContext->state == WebsocketStateShortLength ? 2 : 8);
|
||||
while (encodingContext->lengthAndMaskPosition < lenLength)
|
||||
{
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, (char*)buffer, 1);
|
||||
if (status <= 0)
|
||||
return (effectiveDataLen > 0 ? effectiveDataLen : status);
|
||||
|
||||
encodingContext->payloadLength =
|
||||
(encodingContext->payloadLength) << 8 | buffer[0];
|
||||
encodingContext->lengthAndMaskPosition += status;
|
||||
}
|
||||
encodingContext->state =
|
||||
(encodingContext->masking ? WebSocketStateMaskingKey : WebSocketStatePayload);
|
||||
}
|
||||
break;
|
||||
case WebSocketStateMaskingKey:
|
||||
{
|
||||
WLog_WARN(
|
||||
TAG, "Websocket Server sends data with masking key. This is against RFC 6455.");
|
||||
return -1;
|
||||
}
|
||||
case WebSocketStatePayload:
|
||||
{
|
||||
status = rdg_websocket_handle_payload(bio, pBuffer, size, encodingContext);
|
||||
if (status < 0)
|
||||
return (effectiveDataLen > 0 ? effectiveDataLen : status);
|
||||
|
||||
effectiveDataLen += status;
|
||||
|
||||
if ((size_t)status == size)
|
||||
return effectiveDataLen;
|
||||
pBuffer += status;
|
||||
size -= status;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* should be unreachable */
|
||||
}
|
||||
|
||||
static int rdg_chuncked_read(BIO* bio, BYTE* pBuffer, size_t size,
|
||||
rdg_http_encoding_chunked_context* encodingContext)
|
||||
{
|
||||
@@ -905,7 +464,7 @@ static int rdg_socket_read(BIO* bio, BYTE* pBuffer, size_t size,
|
||||
|
||||
if (encodingContext->isWebsocketTransport)
|
||||
{
|
||||
return rdg_websocket_read(bio, pBuffer, size, &encodingContext->context.websocket);
|
||||
return websocket_read(bio, pBuffer, size, &encodingContext->context.websocket);
|
||||
}
|
||||
|
||||
switch (encodingContext->httpTransferEncoding)
|
||||
|
||||
@@ -22,21 +22,12 @@
|
||||
|
||||
#include <winpr/wtypes.h>
|
||||
#include <winpr/stream.h>
|
||||
#include <winpr/collections.h>
|
||||
#include <winpr/interlocked.h>
|
||||
|
||||
#include <freerdp/log.h>
|
||||
#include <freerdp/utils/ringbuffer.h>
|
||||
#include <freerdp/api.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/types.h>
|
||||
#include <freerdp/settings.h>
|
||||
/* needed for BIO */
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
typedef struct rdp_rdg rdpRdg;
|
||||
|
||||
#include "http.h"
|
||||
|
||||
FREERDP_LOCAL rdpRdg* rdg_new(rdpContext* context);
|
||||
FREERDP_LOCAL void rdg_free(rdpRdg* rdg);
|
||||
|
||||
|
||||
521
libfreerdp/core/gateway/websocket.c
Normal file
521
libfreerdp/core/gateway/websocket.c
Normal file
@@ -0,0 +1,521 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Websocket Framing
|
||||
*
|
||||
* Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
|
||||
*
|
||||
* 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 "websocket.h"
|
||||
#include <freerdp/log.h>
|
||||
|
||||
#define TAG FREERDP_TAG("core.gateway.websocket")
|
||||
|
||||
BOOL websocket_write_wstream(BIO* bio, wStream* sPacket, WEBSOCKET_OPCODE opcode)
|
||||
{
|
||||
size_t fullLen;
|
||||
int status;
|
||||
wStream* sWS;
|
||||
|
||||
uint32_t maskingKey;
|
||||
|
||||
size_t streamPos;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(sPacket);
|
||||
|
||||
const size_t len = Stream_Length(sPacket);
|
||||
Stream_SetPosition(sPacket, 0);
|
||||
|
||||
if (len > INT_MAX)
|
||||
return FALSE;
|
||||
|
||||
if (len < 126)
|
||||
fullLen = len + 6; /* 2 byte "mini header" + 4 byte masking key */
|
||||
else if (len < 0x10000)
|
||||
fullLen = len + 8; /* 2 byte "mini header" + 2 byte length + 4 byte masking key */
|
||||
else
|
||||
fullLen = len + 14; /* 2 byte "mini header" + 8 byte length + 4 byte masking key */
|
||||
|
||||
sWS = Stream_New(NULL, fullLen);
|
||||
if (!sWS)
|
||||
return FALSE;
|
||||
|
||||
winpr_RAND(&maskingKey, sizeof(maskingKey));
|
||||
|
||||
Stream_Write_UINT8(sWS, WEBSOCKET_FIN_BIT | opcode);
|
||||
if (len < 126)
|
||||
Stream_Write_UINT8(sWS, len | WEBSOCKET_MASK_BIT);
|
||||
else if (len < 0x10000)
|
||||
{
|
||||
Stream_Write_UINT8(sWS, 126 | WEBSOCKET_MASK_BIT);
|
||||
Stream_Write_UINT16_BE(sWS, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream_Write_UINT8(sWS, 127 | WEBSOCKET_MASK_BIT);
|
||||
Stream_Write_UINT32_BE(sWS, 0); /* payload is limited to INT_MAX */
|
||||
Stream_Write_UINT32_BE(sWS, len);
|
||||
}
|
||||
Stream_Write_UINT32(sWS, maskingKey);
|
||||
|
||||
/* mask as much as possible with 32bit access */
|
||||
for (streamPos = 0; streamPos + 4 <= len; streamPos += 4)
|
||||
{
|
||||
uint32_t data;
|
||||
Stream_Read_UINT32(sPacket, data);
|
||||
Stream_Write_UINT32(sWS, data ^ maskingKey);
|
||||
}
|
||||
|
||||
/* mask the rest byte by byte */
|
||||
for (; streamPos < len; streamPos++)
|
||||
{
|
||||
BYTE data;
|
||||
BYTE* partialMask = ((BYTE*)&maskingKey) + (streamPos % 4);
|
||||
Stream_Read_UINT8(sPacket, data);
|
||||
Stream_Write_UINT8(sWS, data ^ *partialMask);
|
||||
}
|
||||
|
||||
Stream_SealLength(sWS);
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_write(bio, Stream_Buffer(sWS), Stream_Length(sWS));
|
||||
Stream_Free(sWS, TRUE);
|
||||
|
||||
if (status != (SSIZE_T)fullLen)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int websocket_write(BIO* bio, const BYTE* buf, int isize, WEBSOCKET_OPCODE opcode)
|
||||
{
|
||||
size_t payloadSize;
|
||||
size_t fullLen;
|
||||
int status;
|
||||
wStream* sWS;
|
||||
|
||||
uint32_t maskingKey;
|
||||
|
||||
int streamPos;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(buf);
|
||||
|
||||
winpr_RAND(&maskingKey, sizeof(maskingKey));
|
||||
|
||||
payloadSize = isize;
|
||||
if ((isize < 0) || (isize > UINT16_MAX))
|
||||
return -1;
|
||||
|
||||
if (payloadSize < 126)
|
||||
fullLen = payloadSize + 6; /* 2 byte "mini header" + 4 byte masking key */
|
||||
else if (payloadSize < 0x10000)
|
||||
fullLen = payloadSize + 8; /* 2 byte "mini header" + 2 byte length + 4 byte masking key */
|
||||
else
|
||||
fullLen = payloadSize + 14; /* 2 byte "mini header" + 8 byte length + 4 byte masking key */
|
||||
|
||||
sWS = Stream_New(NULL, fullLen);
|
||||
if (!sWS)
|
||||
return FALSE;
|
||||
|
||||
Stream_Write_UINT8(sWS, WEBSOCKET_FIN_BIT | opcode);
|
||||
if (payloadSize < 126)
|
||||
Stream_Write_UINT8(sWS, payloadSize | WEBSOCKET_MASK_BIT);
|
||||
else if (payloadSize < 0x10000)
|
||||
{
|
||||
Stream_Write_UINT8(sWS, 126 | WEBSOCKET_MASK_BIT);
|
||||
Stream_Write_UINT16_BE(sWS, payloadSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream_Write_UINT8(sWS, 127 | WEBSOCKET_MASK_BIT);
|
||||
/* biggest packet possible is 0xffff + 0xa, so 32bit is always enough */
|
||||
Stream_Write_UINT32_BE(sWS, 0);
|
||||
Stream_Write_UINT32_BE(sWS, payloadSize);
|
||||
}
|
||||
Stream_Write_UINT32(sWS, maskingKey);
|
||||
|
||||
/* mask as much as possible with 32bit access */
|
||||
for (streamPos = 0; streamPos + 4 <= isize; streamPos += 4)
|
||||
{
|
||||
uint32_t masked = *((const uint32_t*)((const BYTE*)buf + streamPos)) ^ maskingKey;
|
||||
Stream_Write_UINT32(sWS, masked);
|
||||
}
|
||||
|
||||
/* mask the rest byte by byte */
|
||||
for (; streamPos < isize; streamPos++)
|
||||
{
|
||||
BYTE* partialMask = (BYTE*)(&maskingKey) + streamPos % 4;
|
||||
BYTE masked = *((const BYTE*)((const BYTE*)buf + streamPos)) ^ *partialMask;
|
||||
Stream_Write_UINT8(sWS, masked);
|
||||
}
|
||||
|
||||
Stream_SealLength(sWS);
|
||||
|
||||
status = BIO_write(bio, Stream_Buffer(sWS), Stream_Length(sWS));
|
||||
Stream_Free(sWS, TRUE);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
return isize;
|
||||
}
|
||||
|
||||
static int websocket_read_data(BIO* bio, BYTE* pBuffer, size_t size,
|
||||
websocket_context* encodingContext)
|
||||
{
|
||||
int status;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(pBuffer);
|
||||
WINPR_ASSERT(encodingContext);
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
status =
|
||||
BIO_read(bio, pBuffer,
|
||||
(encodingContext->payloadLength < size ? encodingContext->payloadLength : size));
|
||||
if (status <= 0)
|
||||
return status;
|
||||
|
||||
encodingContext->payloadLength -= status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int websocket_read_discard(BIO* bio, websocket_context* encodingContext)
|
||||
{
|
||||
char _dummy[256] = { 0 };
|
||||
int status;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(encodingContext);
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, _dummy, sizeof(_dummy));
|
||||
if (status <= 0)
|
||||
return status;
|
||||
|
||||
encodingContext->payloadLength -= status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int websocket_read_wstream(BIO* bio, wStream* s, websocket_context* encodingContext)
|
||||
{
|
||||
int status;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(s);
|
||||
WINPR_ASSERT(encodingContext);
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
return 0;
|
||||
}
|
||||
if (Stream_GetRemainingCapacity(s) != encodingContext->payloadLength)
|
||||
{
|
||||
WLog_WARN(TAG,
|
||||
"wStream::capacity [%" PRIuz "] != encodingContext::paylaodLangth [%" PRIuz "]",
|
||||
Stream_GetRemainingCapacity(s), encodingContext->payloadLength);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, Stream_Pointer(s), encodingContext->payloadLength);
|
||||
if (status <= 0)
|
||||
return status;
|
||||
|
||||
Stream_Seek(s, status);
|
||||
|
||||
encodingContext->payloadLength -= status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
encodingContext->state = WebsocketStateOpcodeAndFin;
|
||||
Stream_SealLength(s);
|
||||
Stream_SetPosition(s, 0);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static BOOL websocket_reply_close(BIO* bio, wStream* s)
|
||||
{
|
||||
/* write back close */
|
||||
wStream* closeFrame;
|
||||
uint16_t maskingKey1;
|
||||
uint16_t maskingKey2;
|
||||
int status;
|
||||
size_t closeDataLen;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
|
||||
closeDataLen = 0;
|
||||
if (s != NULL && Stream_Length(s) >= 2)
|
||||
closeDataLen = 2;
|
||||
|
||||
closeFrame = Stream_New(NULL, 6 + closeDataLen);
|
||||
if (!closeFrame)
|
||||
return FALSE;
|
||||
|
||||
Stream_Write_UINT8(closeFrame, WEBSOCKET_FIN_BIT | WebsocketCloseOpcode);
|
||||
Stream_Write_UINT8(closeFrame, closeDataLen | WEBSOCKET_MASK_BIT); /* no payload */
|
||||
winpr_RAND(&maskingKey1, sizeof(maskingKey1));
|
||||
winpr_RAND(&maskingKey2, sizeof(maskingKey2));
|
||||
Stream_Write_UINT16(closeFrame, maskingKey1);
|
||||
Stream_Write_UINT16(closeFrame, maskingKey2); /* unused half, max 2 bytes of data */
|
||||
|
||||
if (closeDataLen == 2)
|
||||
{
|
||||
uint16_t data;
|
||||
Stream_Read_UINT16(s, data);
|
||||
Stream_Write_UINT16(closeFrame, data ^ maskingKey1);
|
||||
}
|
||||
Stream_SealLength(closeFrame);
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_write(bio, Stream_Buffer(closeFrame), Stream_Length(closeFrame));
|
||||
Stream_Free(closeFrame, TRUE);
|
||||
|
||||
/* server MUST close socket now. The server is not allowed anymore to
|
||||
* send frames but if he does, nothing bad would happen */
|
||||
if (status < 0)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL websocket_reply_pong(BIO* bio, wStream* s)
|
||||
{
|
||||
wStream* closeFrame;
|
||||
uint32_t maskingKey;
|
||||
int status;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
|
||||
if (s != NULL)
|
||||
return websocket_write_wstream(bio, s, WebsocketPongOpcode);
|
||||
|
||||
closeFrame = Stream_New(NULL, 6);
|
||||
if (!closeFrame)
|
||||
return FALSE;
|
||||
|
||||
Stream_Write_UINT8(closeFrame, WEBSOCKET_FIN_BIT | WebsocketPongOpcode);
|
||||
Stream_Write_UINT8(closeFrame, 0 | WEBSOCKET_MASK_BIT); /* no payload */
|
||||
winpr_RAND(&maskingKey, sizeof(maskingKey));
|
||||
Stream_Write_UINT32(closeFrame, maskingKey); /* dummy masking key. */
|
||||
Stream_SealLength(closeFrame);
|
||||
|
||||
ERR_clear_error();
|
||||
status = BIO_write(bio, Stream_Buffer(closeFrame), Stream_Length(closeFrame));
|
||||
Stream_Free(closeFrame, TRUE);
|
||||
|
||||
if (status < 0)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static int websocket_handle_payload(BIO* bio, BYTE* pBuffer, size_t size,
|
||||
websocket_context* encodingContext)
|
||||
{
|
||||
int status;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(pBuffer);
|
||||
WINPR_ASSERT(encodingContext);
|
||||
|
||||
BYTE effectiveOpcode = ((encodingContext->opcode & 0xf) == WebsocketContinuationOpcode
|
||||
? encodingContext->fragmentOriginalOpcode & 0xf
|
||||
: encodingContext->opcode & 0xf);
|
||||
|
||||
switch (effectiveOpcode)
|
||||
{
|
||||
case WebsocketBinaryOpcode:
|
||||
{
|
||||
status = websocket_read_data(bio, pBuffer, size, encodingContext);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
return status;
|
||||
}
|
||||
case WebsocketPingOpcode:
|
||||
{
|
||||
if (encodingContext->responseStreamBuffer == NULL)
|
||||
encodingContext->responseStreamBuffer =
|
||||
Stream_New(NULL, encodingContext->payloadLength);
|
||||
|
||||
status =
|
||||
websocket_read_wstream(bio, encodingContext->responseStreamBuffer, encodingContext);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
if (!encodingContext->closeSent)
|
||||
websocket_reply_pong(bio, encodingContext->responseStreamBuffer);
|
||||
|
||||
Stream_Free(encodingContext->responseStreamBuffer, TRUE);
|
||||
encodingContext->responseStreamBuffer = NULL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WebsocketCloseOpcode:
|
||||
{
|
||||
if (encodingContext->responseStreamBuffer == NULL)
|
||||
encodingContext->responseStreamBuffer =
|
||||
Stream_New(NULL, encodingContext->payloadLength);
|
||||
|
||||
status =
|
||||
websocket_read_wstream(bio, encodingContext->responseStreamBuffer, encodingContext);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
if (encodingContext->payloadLength == 0)
|
||||
{
|
||||
websocket_reply_close(bio, encodingContext->responseStreamBuffer);
|
||||
encodingContext->closeSent = TRUE;
|
||||
|
||||
if (encodingContext->responseStreamBuffer)
|
||||
Stream_Free(encodingContext->responseStreamBuffer, TRUE);
|
||||
encodingContext->responseStreamBuffer = NULL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
WLog_WARN(TAG, "Unimplemented websocket opcode %x. Dropping", effectiveOpcode & 0xf);
|
||||
|
||||
status = websocket_read_discard(bio, encodingContext);
|
||||
if (status < 0)
|
||||
return status;
|
||||
}
|
||||
/* return how many bytes have been written to pBuffer.
|
||||
* Only WebsocketBinaryOpcode writes into it and it returns directly */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int websocket_read(BIO* bio, BYTE* pBuffer, size_t size, websocket_context* encodingContext)
|
||||
{
|
||||
int status;
|
||||
int effectiveDataLen = 0;
|
||||
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(pBuffer);
|
||||
WINPR_ASSERT(encodingContext);
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
switch (encodingContext->state)
|
||||
{
|
||||
case WebsocketStateOpcodeAndFin:
|
||||
{
|
||||
BYTE buffer[1];
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, (char*)buffer, sizeof(buffer));
|
||||
if (status <= 0)
|
||||
return (effectiveDataLen > 0 ? effectiveDataLen : status);
|
||||
|
||||
encodingContext->opcode = buffer[0];
|
||||
if (((encodingContext->opcode & 0xf) != WebsocketContinuationOpcode) &&
|
||||
(encodingContext->opcode & 0xf) < 0x08)
|
||||
encodingContext->fragmentOriginalOpcode = encodingContext->opcode;
|
||||
encodingContext->state = WebsocketStateLengthAndMasking;
|
||||
}
|
||||
break;
|
||||
case WebsocketStateLengthAndMasking:
|
||||
{
|
||||
BYTE buffer[1];
|
||||
BYTE len;
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, (char*)buffer, sizeof(buffer));
|
||||
if (status <= 0)
|
||||
return (effectiveDataLen > 0 ? effectiveDataLen : status);
|
||||
|
||||
encodingContext->masking = ((buffer[0] & WEBSOCKET_MASK_BIT) == WEBSOCKET_MASK_BIT);
|
||||
encodingContext->lengthAndMaskPosition = 0;
|
||||
encodingContext->payloadLength = 0;
|
||||
len = buffer[0] & 0x7f;
|
||||
if (len < 126)
|
||||
{
|
||||
encodingContext->payloadLength = len;
|
||||
encodingContext->state = (encodingContext->masking ? WebSocketStateMaskingKey
|
||||
: WebSocketStatePayload);
|
||||
}
|
||||
else if (len == 126)
|
||||
encodingContext->state = WebsocketStateShortLength;
|
||||
else
|
||||
encodingContext->state = WebsocketStateLongLength;
|
||||
}
|
||||
break;
|
||||
case WebsocketStateShortLength:
|
||||
case WebsocketStateLongLength:
|
||||
{
|
||||
BYTE buffer[1];
|
||||
BYTE lenLength = (encodingContext->state == WebsocketStateShortLength ? 2 : 8);
|
||||
while (encodingContext->lengthAndMaskPosition < lenLength)
|
||||
{
|
||||
ERR_clear_error();
|
||||
status = BIO_read(bio, (char*)buffer, sizeof(buffer));
|
||||
if (status <= 0)
|
||||
return (effectiveDataLen > 0 ? effectiveDataLen : status);
|
||||
|
||||
encodingContext->payloadLength =
|
||||
(encodingContext->payloadLength) << 8 | buffer[0];
|
||||
encodingContext->lengthAndMaskPosition += status;
|
||||
}
|
||||
encodingContext->state =
|
||||
(encodingContext->masking ? WebSocketStateMaskingKey : WebSocketStatePayload);
|
||||
}
|
||||
break;
|
||||
case WebSocketStateMaskingKey:
|
||||
{
|
||||
WLog_WARN(
|
||||
TAG, "Websocket Server sends data with masking key. This is against RFC 6455.");
|
||||
return -1;
|
||||
}
|
||||
case WebSocketStatePayload:
|
||||
{
|
||||
status = websocket_handle_payload(bio, pBuffer, size, encodingContext);
|
||||
if (status < 0)
|
||||
return (effectiveDataLen > 0 ? effectiveDataLen : status);
|
||||
|
||||
effectiveDataLen += status;
|
||||
|
||||
if ((size_t)status == size)
|
||||
return effectiveDataLen;
|
||||
pBuffer += status;
|
||||
size -= status;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* should be unreachable */
|
||||
}
|
||||
71
libfreerdp/core/gateway/websocket.h
Normal file
71
libfreerdp/core/gateway/websocket.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Websocket Framing
|
||||
*
|
||||
* Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
|
||||
*
|
||||
* 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_LIB_CORE_GATEWAY_WEBSOCKET_H
|
||||
#define FREERDP_LIB_CORE_GATEWAY_WEBSOCKET_H
|
||||
|
||||
#include <winpr/wtypes.h>
|
||||
#include <winpr/stream.h>
|
||||
|
||||
#include <freerdp/api.h>
|
||||
|
||||
#include "../../crypto/tls.h"
|
||||
|
||||
#define WEBSOCKET_MASK_BIT 0x80
|
||||
#define WEBSOCKET_FIN_BIT 0x80
|
||||
|
||||
typedef enum
|
||||
{
|
||||
WebsocketContinuationOpcode = 0x0,
|
||||
WebsocketTextOpcode = 0x1,
|
||||
WebsocketBinaryOpcode = 0x2,
|
||||
WebsocketCloseOpcode = 0x8,
|
||||
WebsocketPingOpcode = 0x9,
|
||||
WebsocketPongOpcode = 0xa,
|
||||
} WEBSOCKET_OPCODE;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
WebsocketStateOpcodeAndFin,
|
||||
WebsocketStateLengthAndMasking,
|
||||
WebsocketStateShortLength,
|
||||
WebsocketStateLongLength,
|
||||
WebSocketStateMaskingKey,
|
||||
WebSocketStatePayload,
|
||||
} WEBSOCKET_STATE;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
size_t payloadLength;
|
||||
uint32_t maskingKey;
|
||||
BOOL masking;
|
||||
BOOL closeSent;
|
||||
BYTE opcode;
|
||||
BYTE fragmentOriginalOpcode;
|
||||
BYTE lengthAndMaskPosition;
|
||||
WEBSOCKET_STATE state;
|
||||
wStream* responseStreamBuffer;
|
||||
} websocket_context;
|
||||
|
||||
FREERDP_LOCAL BOOL websocket_write_wstream(BIO* bio, wStream* sPacket, WEBSOCKET_OPCODE opcode);
|
||||
FREERDP_LOCAL int websocket_write(BIO* bio, const BYTE* buf, int isize, WEBSOCKET_OPCODE opcode);
|
||||
FREERDP_LOCAL int websocket_read(BIO* bio, BYTE* pBuffer, size_t size,
|
||||
websocket_context* encodingContext);
|
||||
|
||||
#endif /* FREERDP_LIB_CORE_GATEWAY_WEBSOCKET_H */
|
||||
773
libfreerdp/core/gateway/wst.c
Normal file
773
libfreerdp/core/gateway/wst.c
Normal file
@@ -0,0 +1,773 @@
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#include <winpr/assert.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/print.h>
|
||||
#include <winpr/stream.h>
|
||||
#include <winpr/winsock.h>
|
||||
#include <winpr/cred.h>
|
||||
|
||||
#include <freerdp/log.h>
|
||||
#include <freerdp/error.h>
|
||||
#include <freerdp/utils/ringbuffer.h>
|
||||
#include <freerdp/utils/smartcardlogon.h>
|
||||
|
||||
#include "wst.h"
|
||||
#include "websocket.h"
|
||||
#include "http.h"
|
||||
#include "../credssp_auth.h"
|
||||
#include "../proxy.h"
|
||||
#include "../rdp.h"
|
||||
#include "../../crypto/opensslcompat.h"
|
||||
#include "rpc_fault.h"
|
||||
#include "../utils.h"
|
||||
#include "../settings.h"
|
||||
|
||||
#define TAG FREERDP_TAG("core.gateway.wst")
|
||||
|
||||
#define AUTH_PKG NEGO_SSP_NAME
|
||||
|
||||
struct rdp_wst
|
||||
{
|
||||
rdpContext* context;
|
||||
rdpSettings* settings;
|
||||
BOOL attached;
|
||||
BIO* frontBio;
|
||||
rdpTls* tls;
|
||||
rdpCredsspAuth* auth;
|
||||
HttpContext* http;
|
||||
CRITICAL_SECTION writeSection;
|
||||
char* gwhostname;
|
||||
uint16_t gwport;
|
||||
char* gwpath;
|
||||
websocket_context wscontext;
|
||||
};
|
||||
|
||||
static BOOL wst_get_gateway_credentials(rdpContext* context, rdp_auth_reason reason)
|
||||
{
|
||||
WINPR_ASSERT(context);
|
||||
freerdp* instance = context->instance;
|
||||
|
||||
auth_status rc = utils_authenticate_gateway(instance, reason);
|
||||
switch (rc)
|
||||
{
|
||||
case AUTH_SUCCESS:
|
||||
case AUTH_SKIP:
|
||||
return TRUE;
|
||||
case AUTH_CANCELLED:
|
||||
freerdp_set_last_error_log(instance->context, FREERDP_ERROR_CONNECT_CANCELLED);
|
||||
return FALSE;
|
||||
case AUTH_NO_CREDENTIALS:
|
||||
freerdp_set_last_error_log(instance->context,
|
||||
FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS);
|
||||
return FALSE;
|
||||
case AUTH_FAILED:
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL wst_auth_init(rdpWst* wst, rdpTls* tls, TCHAR* authPkg)
|
||||
{
|
||||
WINPR_ASSERT(wst);
|
||||
WINPR_ASSERT(tls);
|
||||
WINPR_ASSERT(authPkg);
|
||||
|
||||
rdpContext* context = wst->context;
|
||||
rdpSettings* settings = context->settings;
|
||||
SEC_WINNT_AUTH_IDENTITY identity = { 0 };
|
||||
int rc;
|
||||
|
||||
wst->auth = credssp_auth_new(context);
|
||||
if (!wst->auth)
|
||||
return FALSE;
|
||||
|
||||
if (!credssp_auth_init(wst->auth, authPkg, tls->Bindings))
|
||||
return FALSE;
|
||||
|
||||
if (!wst_get_gateway_credentials(context, GW_AUTH_RDG))
|
||||
return FALSE;
|
||||
|
||||
if (!identity_set_from_settings(&identity, settings, FreeRDP_GatewayUsername,
|
||||
FreeRDP_GatewayDomain, FreeRDP_GatewayPassword))
|
||||
return FALSE;
|
||||
|
||||
if (!credssp_auth_setup_client(wst->auth, "HTTP", wst->gwhostname, &identity, NULL))
|
||||
{
|
||||
sspi_FreeAuthIdentity(&identity);
|
||||
return FALSE;
|
||||
}
|
||||
sspi_FreeAuthIdentity(&identity);
|
||||
|
||||
credssp_auth_set_flags(wst->auth, ISC_REQ_CONFIDENTIALITY | ISC_REQ_MUTUAL_AUTH);
|
||||
|
||||
rc = credssp_auth_authenticate(wst->auth);
|
||||
if (rc < 0)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL wst_set_auth_header(rdpCredsspAuth* auth, HttpRequest* request)
|
||||
{
|
||||
WINPR_ASSERT(auth);
|
||||
WINPR_ASSERT(request);
|
||||
|
||||
const SecBuffer* authToken = credssp_auth_get_output_buffer(auth);
|
||||
char* base64AuthToken = NULL;
|
||||
|
||||
if (authToken)
|
||||
{
|
||||
if (authToken->cbBuffer > INT_MAX)
|
||||
return FALSE;
|
||||
|
||||
base64AuthToken = crypto_base64_encode(authToken->pvBuffer, (int)authToken->cbBuffer);
|
||||
}
|
||||
|
||||
if (base64AuthToken)
|
||||
{
|
||||
BOOL rc = http_request_set_auth_scheme(request, credssp_auth_pkg_name(auth)) &&
|
||||
http_request_set_auth_param(request, base64AuthToken);
|
||||
free(base64AuthToken);
|
||||
|
||||
if (!rc)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL wst_recv_auth_token(rdpCredsspAuth* auth, HttpResponse* response)
|
||||
{
|
||||
char buffer[64] = { 0 };
|
||||
size_t len;
|
||||
const char* token64 = NULL;
|
||||
size_t authTokenLength = 0;
|
||||
BYTE* authTokenData = NULL;
|
||||
SecBuffer authToken = { 0 };
|
||||
long StatusCode;
|
||||
int rc;
|
||||
|
||||
if (!auth || !response)
|
||||
return FALSE;
|
||||
|
||||
StatusCode = http_response_get_status_code(response);
|
||||
switch (StatusCode)
|
||||
{
|
||||
case HTTP_STATUS_DENIED:
|
||||
case HTTP_STATUS_OK:
|
||||
break;
|
||||
default:
|
||||
WLog_WARN(TAG, "Unexpected HTTP status: %s",
|
||||
http_status_string_format(StatusCode, buffer, ARRAYSIZE(buffer)));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
token64 = http_response_get_auth_token(response, credssp_auth_pkg_name(auth));
|
||||
|
||||
if (!token64)
|
||||
return FALSE;
|
||||
|
||||
len = strlen(token64);
|
||||
|
||||
crypto_base64_decode(token64, len, &authTokenData, &authTokenLength);
|
||||
|
||||
if (authTokenLength && authTokenData)
|
||||
{
|
||||
authToken.pvBuffer = authTokenData;
|
||||
authToken.cbBuffer = authTokenLength;
|
||||
credssp_auth_take_input_buffer(auth, &authToken);
|
||||
}
|
||||
|
||||
rc = credssp_auth_authenticate(auth);
|
||||
if (rc < 0)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL wst_tls_connect(rdpWst* wst, rdpTls* tls, int timeout)
|
||||
{
|
||||
WINPR_ASSERT(wst);
|
||||
WINPR_ASSERT(tls);
|
||||
int sockfd = 0;
|
||||
long status = 0;
|
||||
BIO* socketBio = NULL;
|
||||
BIO* bufferedBio = NULL;
|
||||
rdpSettings* settings = wst->settings;
|
||||
const char* peerHostname = wst->gwhostname;
|
||||
UINT16 peerPort = wst->gwport;
|
||||
const char *proxyUsername, *proxyPassword;
|
||||
BOOL isProxyConnection =
|
||||
proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
|
||||
|
||||
sockfd = freerdp_tcp_connect(wst->context, peerHostname, peerPort, timeout);
|
||||
|
||||
WLog_ERR(TAG, "connecting to %s %d", peerHostname, peerPort);
|
||||
if (sockfd < 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
socketBio = BIO_new(BIO_s_simple_socket());
|
||||
|
||||
if (!socketBio)
|
||||
{
|
||||
closesocket((SOCKET)sockfd);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
|
||||
bufferedBio = BIO_new(BIO_s_buffered_socket());
|
||||
|
||||
if (!bufferedBio)
|
||||
{
|
||||
BIO_free_all(socketBio);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bufferedBio = BIO_push(bufferedBio, socketBio);
|
||||
status = BIO_set_nonblock(bufferedBio, TRUE);
|
||||
|
||||
if (isProxyConnection)
|
||||
{
|
||||
if (!proxy_connect(settings, bufferedBio, proxyUsername, proxyPassword,
|
||||
settings->GatewayHostname, (UINT16)settings->GatewayPort))
|
||||
{
|
||||
BIO_free_all(bufferedBio);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!status)
|
||||
{
|
||||
BIO_free_all(bufferedBio);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
tls->hostname = wst->gwhostname; // settings->GatewayHostname;
|
||||
tls->port = wst->gwport; //(int)settings->GatewayPort;
|
||||
tls->isGatewayTransport = TRUE;
|
||||
status = freerdp_tls_connect(tls, bufferedBio);
|
||||
if (status < 1)
|
||||
{
|
||||
rdpContext* context = wst->context;
|
||||
if (status < 0)
|
||||
{
|
||||
freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
|
||||
}
|
||||
else
|
||||
{
|
||||
freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
return (status >= 1);
|
||||
}
|
||||
|
||||
static wStream* wst_build_http_request(rdpWst* wst)
|
||||
{
|
||||
wStream* s = NULL;
|
||||
HttpRequest* request = NULL;
|
||||
const char* uri;
|
||||
|
||||
if (!wst)
|
||||
return NULL;
|
||||
|
||||
uri = http_context_get_uri(wst->http);
|
||||
request = http_request_new();
|
||||
|
||||
if (!request)
|
||||
return NULL;
|
||||
|
||||
if (!http_request_set_method(request, "GET") || !http_request_set_uri(request, uri))
|
||||
goto out;
|
||||
|
||||
if (wst->auth)
|
||||
{
|
||||
if (!wst_set_auth_header(wst->auth, request))
|
||||
goto out;
|
||||
}
|
||||
else if (wst->settings->GatewayHttpExtAuthBearer)
|
||||
{
|
||||
http_request_set_auth_scheme(request, "Bearer");
|
||||
http_request_set_auth_param(request, wst->settings->GatewayHttpExtAuthBearer);
|
||||
}
|
||||
|
||||
s = http_request_write(wst->http, request);
|
||||
out:
|
||||
http_request_free(request);
|
||||
|
||||
if (s)
|
||||
Stream_SealLength(s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static BOOL wst_send_http_request(rdpWst* wst, rdpTls* tls)
|
||||
{
|
||||
size_t sz;
|
||||
wStream* s = NULL;
|
||||
int status = -1;
|
||||
WINPR_ASSERT(wst);
|
||||
WINPR_ASSERT(tls);
|
||||
|
||||
s = wst_build_http_request(wst);
|
||||
|
||||
if (!s)
|
||||
return FALSE;
|
||||
|
||||
sz = Stream_Length(s);
|
||||
|
||||
if (sz <= INT_MAX)
|
||||
status = freerdp_tls_write_all(tls, Stream_Buffer(s), (int)sz);
|
||||
|
||||
Stream_Free(s, TRUE);
|
||||
return (status >= 0);
|
||||
}
|
||||
|
||||
BOOL wst_connect(rdpWst* wst, DWORD timeout)
|
||||
{
|
||||
HttpResponse* response = NULL;
|
||||
long StatusCode;
|
||||
|
||||
WINPR_ASSERT(wst);
|
||||
if (!wst_tls_connect(wst, wst->tls, timeout))
|
||||
return FALSE;
|
||||
|
||||
if (!wst_send_http_request(wst, wst->tls))
|
||||
return FALSE;
|
||||
|
||||
response = http_response_recv(wst->tls, TRUE);
|
||||
if (!response)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
StatusCode = http_response_get_status_code(response);
|
||||
|
||||
switch (StatusCode)
|
||||
{
|
||||
case HTTP_STATUS_NOT_FOUND:
|
||||
case HTTP_STATUS_BAD_METHOD:
|
||||
{
|
||||
http_response_free(response);
|
||||
return FALSE;
|
||||
}
|
||||
case HTTP_STATUS_DENIED:
|
||||
{
|
||||
/* if the response is HTTP_STATUS_DENIED and no bearer authentication was used, try with
|
||||
* credssp */
|
||||
http_response_free(response);
|
||||
if (wst->settings->GatewayHttpExtAuthBearer)
|
||||
return FALSE;
|
||||
|
||||
if (!wst_auth_init(wst, wst->tls, AUTH_PKG))
|
||||
return FALSE;
|
||||
if (!wst_send_http_request(wst, wst->tls))
|
||||
return FALSE;
|
||||
|
||||
response = http_response_recv(wst->tls, TRUE);
|
||||
if (!response)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
while (!credssp_auth_is_complete(wst->auth))
|
||||
{
|
||||
if (!wst_recv_auth_token(wst->auth, response))
|
||||
{
|
||||
http_response_free(response);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (credssp_auth_have_output_token(wst->auth))
|
||||
{
|
||||
http_response_free(response);
|
||||
|
||||
if (!wst_send_http_request(wst, wst->tls))
|
||||
return FALSE;
|
||||
|
||||
response = http_response_recv(wst->tls, TRUE);
|
||||
if (!response)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
StatusCode = http_response_get_status_code(response);
|
||||
credssp_auth_free(wst->auth);
|
||||
wst->auth = NULL;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
http_response_free(response);
|
||||
|
||||
if (StatusCode == HTTP_STATUS_SWITCH_PROTOCOLS)
|
||||
{
|
||||
wst->wscontext.state = WebsocketStateOpcodeAndFin;
|
||||
wst->wscontext.responseStreamBuffer = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
char buffer[64] = { 0 };
|
||||
WLog_ERR(TAG, "Unexpected HTTP status: %s",
|
||||
http_status_string_format(StatusCode, buffer, ARRAYSIZE(buffer)));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD wst_get_event_handles(rdpWst* wst, HANDLE* events, DWORD count)
|
||||
{
|
||||
DWORD nCount = 0;
|
||||
WINPR_ASSERT(wst != NULL);
|
||||
|
||||
if (wst->tls)
|
||||
{
|
||||
if (events && (nCount < count))
|
||||
{
|
||||
BIO_get_event(wst->tls->bio, &events[nCount]);
|
||||
nCount++;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
return nCount;
|
||||
}
|
||||
|
||||
static int wst_bio_write(BIO* bio, const char* buf, int num)
|
||||
{
|
||||
int status;
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(buf);
|
||||
|
||||
rdpWst* wst = (rdpWst*)BIO_get_data(bio);
|
||||
WINPR_ASSERT(wst);
|
||||
BIO_clear_flags(bio, BIO_FLAGS_WRITE);
|
||||
EnterCriticalSection(&wst->writeSection);
|
||||
status = websocket_write(wst->tls->bio, (const BYTE*)buf, num, WebsocketBinaryOpcode);
|
||||
LeaveCriticalSection(&wst->writeSection);
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY);
|
||||
return -1;
|
||||
}
|
||||
else if (status < num)
|
||||
{
|
||||
BIO_set_flags(bio, BIO_FLAGS_WRITE);
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
}
|
||||
else
|
||||
{
|
||||
BIO_set_flags(bio, BIO_FLAGS_WRITE);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int wst_bio_read(BIO* bio, char* buf, int size)
|
||||
{
|
||||
int status = 0;
|
||||
WINPR_ASSERT(bio);
|
||||
WINPR_ASSERT(buf);
|
||||
|
||||
rdpWst* wst = (rdpWst*)BIO_get_data(bio);
|
||||
WINPR_ASSERT(wst);
|
||||
|
||||
while (status <= 0)
|
||||
{
|
||||
status = websocket_read(wst->tls->bio, (BYTE*)buf, size, &wst->wscontext);
|
||||
if (status <= 0)
|
||||
{
|
||||
if (!BIO_should_retry(wst->tls->bio))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (status < 0)
|
||||
{
|
||||
BIO_clear_retry_flags(bio);
|
||||
return -1;
|
||||
}
|
||||
else if (status == 0)
|
||||
{
|
||||
BIO_set_retry_read(bio);
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
BIO_set_flags(bio, BIO_FLAGS_READ);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int wst_bio_puts(BIO* bio, const char* str)
|
||||
{
|
||||
WINPR_UNUSED(bio);
|
||||
WINPR_UNUSED(str);
|
||||
return -2;
|
||||
}
|
||||
|
||||
static int wst_bio_gets(BIO* bio, char* str, int size)
|
||||
{
|
||||
WINPR_UNUSED(bio);
|
||||
WINPR_UNUSED(str);
|
||||
WINPR_UNUSED(size);
|
||||
return -2;
|
||||
}
|
||||
|
||||
static long wst_bio_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
|
||||
{
|
||||
long status = -1;
|
||||
WINPR_ASSERT(bio);
|
||||
|
||||
rdpWst* wst = (rdpWst*)BIO_get_data(bio);
|
||||
WINPR_ASSERT(wst);
|
||||
rdpTls* tls = wst->tls;
|
||||
|
||||
if (cmd == BIO_CTRL_FLUSH)
|
||||
{
|
||||
(void)BIO_flush(tls->bio);
|
||||
status = 1;
|
||||
}
|
||||
else if (cmd == BIO_C_SET_NONBLOCK)
|
||||
{
|
||||
status = 1;
|
||||
}
|
||||
else if (cmd == BIO_C_READ_BLOCKED)
|
||||
{
|
||||
status = BIO_read_blocked(tls->bio);
|
||||
}
|
||||
else if (cmd == BIO_C_WRITE_BLOCKED)
|
||||
{
|
||||
status = BIO_write_blocked(tls->bio);
|
||||
}
|
||||
else if (cmd == BIO_C_WAIT_READ)
|
||||
{
|
||||
int timeout = (int)arg1;
|
||||
|
||||
if (BIO_read_blocked(tls->bio))
|
||||
return BIO_wait_read(tls->bio, timeout);
|
||||
status = 1;
|
||||
}
|
||||
else if (cmd == BIO_C_WAIT_WRITE)
|
||||
{
|
||||
int timeout = (int)arg1;
|
||||
|
||||
if (BIO_write_blocked(tls->bio))
|
||||
status = BIO_wait_write(tls->bio, timeout);
|
||||
else
|
||||
status = 1;
|
||||
}
|
||||
else if (cmd == BIO_C_GET_EVENT || cmd == BIO_C_GET_FD)
|
||||
{
|
||||
status = BIO_ctrl(tls->bio, cmd, arg1, arg2);
|
||||
}
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||
else if (cmd == BIO_CTRL_GET_KTLS_SEND)
|
||||
{
|
||||
/* Even though BIO_get_ktls_send says that returning negative values is valid
|
||||
* openssl internal sources are full of if(!BIO_get_ktls_send && ) stuff. This has some
|
||||
* nasty sideeffects. return 0 as proper no KTLS offloading flag
|
||||
*/
|
||||
status = 0;
|
||||
}
|
||||
else if (cmd == BIO_CTRL_GET_KTLS_RECV)
|
||||
{
|
||||
/* Even though BIO_get_ktls_recv says that returning negative values is valid
|
||||
* there is no reason to trust trust negative values are implemented right everywhere
|
||||
*/
|
||||
status = 0;
|
||||
}
|
||||
#endif
|
||||
return status;
|
||||
}
|
||||
|
||||
static int wst_bio_new(BIO* bio)
|
||||
{
|
||||
BIO_set_init(bio, 1);
|
||||
BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int wst_bio_free(BIO* bio)
|
||||
{
|
||||
WINPR_UNUSED(bio);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static BIO_METHOD* BIO_s_wst(void)
|
||||
{
|
||||
static BIO_METHOD* bio_methods = NULL;
|
||||
|
||||
if (bio_methods == NULL)
|
||||
{
|
||||
if (!(bio_methods = BIO_meth_new(BIO_TYPE_TSG, "WSTransport")))
|
||||
return NULL;
|
||||
|
||||
BIO_meth_set_write(bio_methods, wst_bio_write);
|
||||
BIO_meth_set_read(bio_methods, wst_bio_read);
|
||||
BIO_meth_set_puts(bio_methods, wst_bio_puts);
|
||||
BIO_meth_set_gets(bio_methods, wst_bio_gets);
|
||||
BIO_meth_set_ctrl(bio_methods, wst_bio_ctrl);
|
||||
BIO_meth_set_create(bio_methods, wst_bio_new);
|
||||
BIO_meth_set_destroy(bio_methods, wst_bio_free);
|
||||
}
|
||||
|
||||
return bio_methods;
|
||||
}
|
||||
|
||||
static BOOL wst_parse_url(rdpWst* wst, const char* url)
|
||||
{
|
||||
const char* hostStart;
|
||||
const char* pos;
|
||||
WINPR_ASSERT(wst);
|
||||
WINPR_ASSERT(url);
|
||||
|
||||
free(wst->gwhostname);
|
||||
wst->gwhostname = NULL;
|
||||
free(wst->gwpath);
|
||||
wst->gwpath = NULL;
|
||||
|
||||
if (strncmp("wss://", url, 6) != 0)
|
||||
{
|
||||
WLog_ERR(TAG, "Websocket URL is invalid. Only wss:// URLs are supported");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
hostStart = url + 6;
|
||||
pos = hostStart;
|
||||
while (*pos != '\0' && *pos != ':' && *pos != '/')
|
||||
pos++;
|
||||
free(wst->gwhostname);
|
||||
wst->gwhostname = NULL;
|
||||
if (pos - hostStart == 0)
|
||||
return FALSE;
|
||||
wst->gwhostname = malloc(sizeof(char) * (pos - hostStart + 1));
|
||||
if (!wst->gwhostname)
|
||||
return FALSE;
|
||||
strncpy(wst->gwhostname, hostStart, (pos - hostStart));
|
||||
wst->gwhostname[pos - hostStart] = '\0';
|
||||
|
||||
if (*pos == ':')
|
||||
{
|
||||
char port[6];
|
||||
char* portNumberEnd;
|
||||
pos++;
|
||||
const char* portStart = pos;
|
||||
while (*pos != '\0' && *pos != '/')
|
||||
pos++;
|
||||
if (pos - portStart > 5 || pos - portStart == 0)
|
||||
return FALSE;
|
||||
strncpy(port, portStart, (pos - portStart));
|
||||
port[pos - portStart] = '\0';
|
||||
int _p = strtol(port, &portNumberEnd, 10);
|
||||
if (portNumberEnd && *portNumberEnd == '\0' && _p > 0 && _p <= UINT16_MAX)
|
||||
wst->gwport = _p;
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
wst->gwport = 443;
|
||||
wst->gwpath = strdup(pos);
|
||||
if (!wst->gwpath)
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
rdpWst* wst_new(rdpContext* context)
|
||||
{
|
||||
rdpWst* wst;
|
||||
|
||||
if (!context)
|
||||
return NULL;
|
||||
|
||||
wst = (rdpWst*)calloc(1, sizeof(rdpWst));
|
||||
|
||||
if (wst)
|
||||
{
|
||||
wst->context = context;
|
||||
wst->settings = wst->context->settings;
|
||||
|
||||
wst->gwhostname = NULL;
|
||||
wst->gwport = 443;
|
||||
wst->gwpath = NULL;
|
||||
|
||||
if (!wst_parse_url(wst, context->settings->GatewayUrl))
|
||||
goto wst_alloc_error;
|
||||
|
||||
wst->tls = freerdp_tls_new(wst->settings);
|
||||
|
||||
wst->http = http_context_new();
|
||||
|
||||
if (!wst->http)
|
||||
goto wst_alloc_error;
|
||||
|
||||
if (!http_context_set_uri(wst->http, wst->gwpath) ||
|
||||
!http_context_set_accept(wst->http, "*/*") ||
|
||||
!http_context_set_cache_control(wst->http, "no-cache") ||
|
||||
!http_context_set_pragma(wst->http, "no-cache") ||
|
||||
!http_context_set_connection(wst->http, "Keep-Alive") ||
|
||||
!http_context_set_user_agent(wst->http, "FreeRDP/3.0") ||
|
||||
!http_context_set_host(wst->http, wst->gwhostname) ||
|
||||
!http_context_enable_websocket_upgrade(wst->http, TRUE))
|
||||
{
|
||||
goto wst_alloc_error;
|
||||
}
|
||||
|
||||
wst->frontBio = BIO_new(BIO_s_wst());
|
||||
|
||||
if (!wst->frontBio)
|
||||
goto wst_alloc_error;
|
||||
|
||||
BIO_set_data(wst->frontBio, wst);
|
||||
InitializeCriticalSection(&wst->writeSection);
|
||||
wst->auth = NULL;
|
||||
}
|
||||
|
||||
return wst;
|
||||
wst_alloc_error:
|
||||
wst_free(wst);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void wst_free(rdpWst* wst)
|
||||
{
|
||||
if (!wst)
|
||||
return;
|
||||
|
||||
freerdp_tls_free(wst->tls);
|
||||
http_context_free(wst->http);
|
||||
credssp_auth_free(wst->auth);
|
||||
free(wst->gwhostname);
|
||||
free(wst->gwpath);
|
||||
|
||||
if (!wst->attached)
|
||||
BIO_free_all(wst->frontBio);
|
||||
|
||||
DeleteCriticalSection(&wst->writeSection);
|
||||
|
||||
if (wst->wscontext.responseStreamBuffer != NULL)
|
||||
Stream_Free(wst->wscontext.responseStreamBuffer, TRUE);
|
||||
|
||||
free(wst);
|
||||
}
|
||||
|
||||
BIO* wst_get_front_bio_and_take_ownership(rdpWst* wst)
|
||||
{
|
||||
if (!wst)
|
||||
return NULL;
|
||||
|
||||
wst->attached = TRUE;
|
||||
return wst->frontBio;
|
||||
}
|
||||
39
libfreerdp/core/gateway/wst.h
Normal file
39
libfreerdp/core/gateway/wst.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Websocket Transport
|
||||
*
|
||||
* Copyright 2023 Michael Saxl <mike@mwsys.mine.bz>
|
||||
*
|
||||
* 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_LIB_CORE_GATEWAY_WEBSOCKET_TRANSPORT_H
|
||||
#define FREERDP_LIB_CORE_GATEWAY_WEBSOCKET_TRANSPORT_H
|
||||
|
||||
#include <winpr/wtypes.h>
|
||||
#include <winpr/stream.h>
|
||||
|
||||
/* needed for BIO */
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
typedef struct rdp_wst rdpWst;
|
||||
|
||||
FREERDP_LOCAL rdpWst* wst_new(rdpContext* context);
|
||||
FREERDP_LOCAL void wst_free(rdpWst* wst);
|
||||
|
||||
FREERDP_LOCAL BIO* wst_get_front_bio_and_take_ownership(rdpWst* wst);
|
||||
|
||||
FREERDP_LOCAL BOOL wst_connect(rdpWst* wst, DWORD timeout);
|
||||
FREERDP_LOCAL DWORD wst_get_event_handles(rdpWst* wst, HANDLE* events, DWORD count);
|
||||
|
||||
#endif /* FREERDP_LIB_CORE_GATEWAY_WEBSOCKET_TRANSPORT_H */
|
||||
@@ -370,7 +370,9 @@ static const size_t string_list_indices[] = {
|
||||
FreeRDP_GatewayAccessToken,
|
||||
FreeRDP_GatewayDomain,
|
||||
FreeRDP_GatewayHostname,
|
||||
FreeRDP_GatewayHttpExtAuthBearer,
|
||||
FreeRDP_GatewayPassword,
|
||||
FreeRDP_GatewayUrl,
|
||||
FreeRDP_GatewayUsername,
|
||||
FreeRDP_HomePath,
|
||||
FreeRDP_ImeFileName,
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
#include "utils.h"
|
||||
#include "state.h"
|
||||
|
||||
#include "gateway/rdg.h"
|
||||
#include "gateway/wst.h"
|
||||
|
||||
#define TAG FREERDP_TAG("core.transport")
|
||||
|
||||
#define BUFFER_SIZE 16384
|
||||
@@ -64,6 +67,7 @@ struct rdp_transport
|
||||
BIO* frontBio;
|
||||
rdpRdg* rdg;
|
||||
rdpTsg* tsg;
|
||||
rdpWst* wst;
|
||||
rdpTls* tls;
|
||||
rdpContext* context;
|
||||
rdpNla* nla;
|
||||
@@ -463,8 +467,33 @@ BOOL transport_connect(rdpTransport* transport, const char* hostname, UINT16 por
|
||||
|
||||
if (transport->GatewayEnabled)
|
||||
{
|
||||
if (settings->GatewayUrl)
|
||||
{
|
||||
WINPR_ASSERT(!transport->wst);
|
||||
transport->wst = wst_new(context);
|
||||
|
||||
if (!transport->wst)
|
||||
return FALSE;
|
||||
|
||||
status = wst_connect(transport->wst, timeout);
|
||||
|
||||
if (status)
|
||||
{
|
||||
transport->frontBio = wst_get_front_bio_and_take_ownership(transport->wst);
|
||||
WINPR_ASSERT(transport->frontBio);
|
||||
BIO_set_nonblock(transport->frontBio, 0);
|
||||
transport->layer = TRANSPORT_LAYER_TSG;
|
||||
status = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
wst_free(transport->wst);
|
||||
transport->wst = NULL;
|
||||
}
|
||||
}
|
||||
if (!status && settings->GatewayHttpTransport)
|
||||
{
|
||||
WINPR_ASSERT(!transport->rdg);
|
||||
transport->rdg = rdg_new(context);
|
||||
|
||||
if (!transport->rdg)
|
||||
@@ -489,6 +518,7 @@ BOOL transport_connect(rdpTransport* transport, const char* hostname, UINT16 por
|
||||
|
||||
if (!status && settings->GatewayRpcTransport && rpcFallback)
|
||||
{
|
||||
WINPR_ASSERT(!transport->tsg);
|
||||
transport->tsg = tsg_new(transport);
|
||||
|
||||
if (!transport->tsg)
|
||||
@@ -1230,6 +1260,16 @@ DWORD transport_get_event_handles(rdpTransport* transport, HANDLE* events, DWORD
|
||||
|
||||
nCount += tmp;
|
||||
}
|
||||
else if (transport->wst)
|
||||
{
|
||||
const DWORD tmp =
|
||||
wst_get_event_handles(transport->wst, &events[nCount], count - nCount);
|
||||
|
||||
if (tmp == 0)
|
||||
return 0;
|
||||
|
||||
nCount += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return nCount;
|
||||
|
||||
@@ -34,7 +34,6 @@ typedef enum
|
||||
#include "rdstls.h"
|
||||
|
||||
#include "gateway/tsg.h"
|
||||
#include "gateway/rdg.h"
|
||||
|
||||
#include <winpr/sspi.h>
|
||||
#include <winpr/wlog.h>
|
||||
|
||||
Reference in New Issue
Block a user