diff options
Diffstat (limited to 'libfreerdp/core/childsession.c')
-rw-r--r-- | libfreerdp/core/childsession.c | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/libfreerdp/core/childsession.c b/libfreerdp/core/childsession.c new file mode 100644 index 0000000..3bed262 --- /dev/null +++ b/libfreerdp/core/childsession.c @@ -0,0 +1,338 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Named pipe transport + * + * Copyright 2023 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 "tcp.h" +#include <winpr/library.h> +#include <winpr/assert.h> +#include "childsession.h" + +#define TAG FREERDP_TAG("childsession") + +typedef struct +{ + HANDLE hFile; +} WINPR_BIO_NAMED; + +static int transport_bio_named_uninit(BIO* bio); + +static int transport_bio_named_write(BIO* bio, const char* buf, int size) +{ + WINPR_ASSERT(bio); + WINPR_ASSERT(buf); + + WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio); + + if (!buf) + return 0; + + BIO_clear_flags(bio, BIO_FLAGS_WRITE); + DWORD written = 0; + + BOOL ret = WriteFile(ptr->hFile, buf, size, &written, NULL); + WLog_VRB(TAG, "transport_bio_named_write(%d)=%d written=%d", size, ret, written); + + if (!ret) + return -1; + + if (written == 0) + return -1; + + return written; +} + +static int transport_bio_named_read(BIO* bio, char* buf, int size) +{ + WINPR_ASSERT(bio); + WINPR_ASSERT(buf); + + WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio); + + if (!buf) + return 0; + + BIO_clear_flags(bio, BIO_FLAGS_READ); + + DWORD readBytes = 0; + BOOL ret = ReadFile(ptr->hFile, buf, size, &readBytes, NULL); + WLog_VRB(TAG, "transport_bio_named_read(%d)=%d read=%d", size, ret, readBytes); + if (!ret) + { + if (GetLastError() == ERROR_NO_DATA) + BIO_set_flags(bio, (BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ)); + return -1; + } + + if (readBytes == 0) + { + BIO_set_flags(bio, (BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY)); + return 0; + } + + return readBytes; +} + +static int transport_bio_named_puts(BIO* bio, const char* str) +{ + WINPR_ASSERT(bio); + WINPR_ASSERT(str); + + return transport_bio_named_write(bio, str, strlen(str)); +} + +static int transport_bio_named_gets(BIO* bio, char* str, int size) +{ + WINPR_ASSERT(bio); + WINPR_ASSERT(str); + + return transport_bio_named_write(bio, str, size); +} + +static long transport_bio_named_ctrl(BIO* bio, int cmd, long arg1, void* arg2) +{ + WINPR_ASSERT(bio); + + int status = -1; + WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio); + + switch (cmd) + { + case BIO_C_SET_SOCKET: + case BIO_C_GET_SOCKET: + return -1; + case BIO_C_GET_EVENT: + if (!BIO_get_init(bio) || !arg2) + return 0; + + *((HANDLE*)arg2) = ptr->hFile; + return 1; + case BIO_C_SET_HANDLE: + BIO_set_init(bio, 1); + if (!BIO_get_init(bio) || !arg2) + return 0; + + ptr->hFile = (HANDLE)arg2; + return 1; + case BIO_C_SET_NONBLOCK: + { + return 1; + } + case BIO_C_WAIT_READ: + { + return 1; + } + + case BIO_C_WAIT_WRITE: + { + return 1; + } + + default: + break; + } + + switch (cmd) + { + case BIO_CTRL_GET_CLOSE: + status = BIO_get_shutdown(bio); + break; + + case BIO_CTRL_SET_CLOSE: + BIO_set_shutdown(bio, (int)arg1); + status = 1; + break; + + case BIO_CTRL_DUP: + status = 1; + break; + + case BIO_CTRL_FLUSH: + status = 1; + break; + + default: + status = 0; + break; + } + + return status; +} + +static int transport_bio_named_uninit(BIO* bio) +{ + WINPR_ASSERT(bio); + WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio); + + if (ptr && ptr->hFile) + { + CloseHandle(ptr->hFile); + ptr->hFile = NULL; + } + + BIO_set_init(bio, 0); + BIO_set_flags(bio, 0); + return 1; +} + +static int transport_bio_named_new(BIO* bio) +{ + WINPR_ASSERT(bio); + BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY); + + WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)calloc(1, sizeof(WINPR_BIO_NAMED)); + if (!ptr) + return 0; + + BIO_set_data(bio, ptr); + BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY); + return 1; +} + +static int transport_bio_named_free(BIO* bio) +{ + WINPR_BIO_NAMED* ptr = NULL; + + if (!bio) + return 0; + + transport_bio_named_uninit(bio); + ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio); + + if (ptr) + { + BIO_set_data(bio, NULL); + free(ptr); + } + + return 1; +} + +static BIO_METHOD* BIO_s_namedpipe(void) +{ + static BIO_METHOD* bio_methods = NULL; + + if (bio_methods == NULL) + { + if (!(bio_methods = BIO_meth_new(BIO_TYPE_NAMEDPIPE, "NamedPipe"))) + return NULL; + + BIO_meth_set_write(bio_methods, transport_bio_named_write); + BIO_meth_set_read(bio_methods, transport_bio_named_read); + BIO_meth_set_puts(bio_methods, transport_bio_named_puts); + BIO_meth_set_gets(bio_methods, transport_bio_named_gets); + BIO_meth_set_ctrl(bio_methods, transport_bio_named_ctrl); + BIO_meth_set_create(bio_methods, transport_bio_named_new); + BIO_meth_set_destroy(bio_methods, transport_bio_named_free); + } + + return bio_methods; +} + +typedef NTSTATUS (*WinStationCreateChildSessionTransportFn)(WCHAR* path, DWORD len); +static BOOL createChildSessionTransport(HANDLE* pFile) +{ + WINPR_ASSERT(pFile); + + HANDLE hModule = NULL; + BOOL ret = FALSE; + *pFile = INVALID_HANDLE_VALUE; + + BOOL childEnabled = 0; + if (!WTSIsChildSessionsEnabled(&childEnabled)) + { + WLog_ERR(TAG, "error when calling WTSIsChildSessionsEnabled"); + goto out; + } + + if (!childEnabled) + { + WLog_INFO(TAG, "child sessions aren't enabled"); + if (!WTSEnableChildSessions(TRUE)) + { + WLog_ERR(TAG, "error when calling WTSEnableChildSessions"); + goto out; + } + WLog_INFO(TAG, "successfully enabled child sessions"); + } + + hModule = LoadLibraryA("winsta.dll"); + if (!hModule) + return FALSE; + WCHAR pipePath[0x80] = { 0 }; + char pipePathA[0x80] = { 0 }; + + WinStationCreateChildSessionTransportFn createChildSessionFn = + (WinStationCreateChildSessionTransportFn)GetProcAddress( + hModule, "WinStationCreateChildSessionTransport"); + if (!createChildSessionFn) + { + WLog_ERR(TAG, "unable to retrieve WinStationCreateChildSessionTransport function"); + goto out; + } + + HRESULT hStatus = createChildSessionFn(pipePath, 0x80); + if (!SUCCEEDED(hStatus)) + { + WLog_ERR(TAG, "error 0x%x when creating childSessionTransport", hStatus); + goto out; + } + + ConvertWCharNToUtf8(pipePath, 0x80, pipePathA, sizeof(pipePathA)); + WLog_DBG(TAG, "child session is at '%s'", pipePathA); + + HANDLE f = CreateFileW(pipePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (f == INVALID_HANDLE_VALUE) + { + WLog_ERR(TAG, "error when connecting to local named pipe"); + goto out; + } + + *pFile = f; + ret = TRUE; + +out: + FreeLibrary(hModule); + return ret; +} + +BIO* createChildSessionBio(void) +{ + HANDLE f = INVALID_HANDLE_VALUE; + if (!createChildSessionTransport(&f)) + return NULL; + + BIO* lowLevelBio = BIO_new(BIO_s_namedpipe()); + if (!lowLevelBio) + { + CloseHandle(f); + return NULL; + } + + BIO_set_handle(lowLevelBio, f); + BIO* bufferedBio = BIO_new(BIO_s_buffered_socket()); + + if (!bufferedBio) + { + BIO_free_all(lowLevelBio); + return FALSE; + } + + bufferedBio = BIO_push(bufferedBio, lowLevelBio); + + return bufferedBio; +} |