diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /channels/rdpdr | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'channels/rdpdr')
-rw-r--r-- | channels/rdpdr/CMakeLists.txt | 26 | ||||
-rw-r--r-- | channels/rdpdr/ChannelOptions.cmake | 13 | ||||
-rw-r--r-- | channels/rdpdr/client/CMakeLists.txt | 42 | ||||
-rw-r--r-- | channels/rdpdr/client/devman.c | 236 | ||||
-rw-r--r-- | channels/rdpdr/client/devman.h | 36 | ||||
-rw-r--r-- | channels/rdpdr/client/irp.c | 165 | ||||
-rw-r--r-- | channels/rdpdr/client/irp.h | 29 | ||||
-rw-r--r-- | channels/rdpdr/client/rdpdr_capabilities.c | 259 | ||||
-rw-r--r-- | channels/rdpdr/client/rdpdr_capabilities.h | 31 | ||||
-rw-r--r-- | channels/rdpdr/client/rdpdr_main.c | 2342 | ||||
-rw-r--r-- | channels/rdpdr/client/rdpdr_main.h | 121 | ||||
-rw-r--r-- | channels/rdpdr/server/CMakeLists.txt | 29 | ||||
-rw-r--r-- | channels/rdpdr/server/rdpdr_main.c | 3574 | ||||
-rw-r--r-- | channels/rdpdr/server/rdpdr_main.h | 47 |
14 files changed, 6950 insertions, 0 deletions
diff --git a/channels/rdpdr/CMakeLists.txt b/channels/rdpdr/CMakeLists.txt new file mode 100644 index 0000000..e9b4181 --- /dev/null +++ b/channels/rdpdr/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP 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. + +define_channel("rdpdr") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rdpdr/ChannelOptions.cmake b/channels/rdpdr/ChannelOptions.cmake new file mode 100644 index 0000000..9a96676 --- /dev/null +++ b/channels/rdpdr/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "rdpdr" TYPE "static" + DESCRIPTION "Device Redirection Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEFS] [MS-RDPEPC] [MS-RDPESC] [MS-RDPESP]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpdr/client/CMakeLists.txt b/channels/rdpdr/client/CMakeLists.txt new file mode 100644 index 0000000..7d11574 --- /dev/null +++ b/channels/rdpdr/client/CMakeLists.txt @@ -0,0 +1,42 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> +# Copyright 2016 Inuvika Inc. +# Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.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_channel_client("rdpdr") + +set(${MODULE_PREFIX}_SRCS + irp.c + irp.h + devman.c + devman.h + rdpdr_main.c + rdpdr_main.h + rdpdr_capabilities.c + rdpdr_capabilities.h +) + +set(${MODULE_PREFIX}_LIBS + winpr freerdp +) +if(APPLE AND (NOT IOS)) + find_library(CORESERVICES_LIBRARY CoreServices) + list(APPEND ${MODULE_PREFIX}_LIBS + ${CORESERVICES_LIBRARY} + ) +endif() +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") diff --git a/channels/rdpdr/client/devman.c b/channels/rdpdr/client/devman.c new file mode 100644 index 0000000..7c9e3ff --- /dev/null +++ b/channels/rdpdr/client/devman.c @@ -0,0 +1,236 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2016 Armin Novak <armin.novak@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 <winpr/crt.h> +#include <winpr/stream.h> + +#include <freerdp/types.h> +#include <freerdp/addin.h> +#include <freerdp/client/channels.h> +#include <freerdp/channels/log.h> + +#include "rdpdr_main.h" + +#include "devman.h" + +#define TAG CHANNELS_TAG("rdpdr.client") + +static void devman_device_free(void* obj) +{ + DEVICE* device = (DEVICE*)obj; + + if (!device) + return; + + IFCALL(device->Free, device); +} + +DEVMAN* devman_new(rdpdrPlugin* rdpdr) +{ + DEVMAN* devman = NULL; + + if (!rdpdr) + return NULL; + + devman = (DEVMAN*)calloc(1, sizeof(DEVMAN)); + + if (!devman) + { + WLog_Print(rdpdr->log, WLOG_INFO, "calloc failed!"); + return NULL; + } + + devman->plugin = (void*)rdpdr; + devman->id_sequence = 1; + devman->devices = ListDictionary_New(TRUE); + + if (!devman->devices) + { + WLog_Print(rdpdr->log, WLOG_INFO, "ListDictionary_New failed!"); + free(devman); + return NULL; + } + + ListDictionary_ValueObject(devman->devices)->fnObjectFree = devman_device_free; + return devman; +} + +void devman_free(DEVMAN* devman) +{ + ListDictionary_Free(devman->devices); + free(devman); +} + +void devman_unregister_device(DEVMAN* devman, void* key) +{ + DEVICE* device = NULL; + + if (!devman || !key) + return; + + device = (DEVICE*)ListDictionary_Take(devman->devices, key); + + if (device) + devman_device_free(device); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT devman_register_device(DEVMAN* devman, DEVICE* device) +{ + void* key = NULL; + + if (!devman || !device) + return ERROR_INVALID_PARAMETER; + + device->id = devman->id_sequence++; + key = (void*)(size_t)device->id; + + if (!ListDictionary_Add(devman->devices, key, device)) + { + WLog_INFO(TAG, "ListDictionary_Add failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id) +{ + DEVICE* device = NULL; + void* key = (void*)(size_t)id; + + if (!devman) + { + WLog_ERR(TAG, "device manager=%p", devman); + return NULL; + } + + device = (DEVICE*)ListDictionary_GetItemValue(devman->devices, key); + if (!device) + WLog_WARN(TAG, "could not find device ID 0x%08" PRIx32, id); + return device; +} + +DEVICE* devman_get_device_by_type(DEVMAN* devman, UINT32 type) +{ + DEVICE* device = NULL; + ULONG_PTR* keys = NULL; + + if (!devman) + return NULL; + + ListDictionary_Lock(devman->devices); + const size_t count = ListDictionary_GetKeys(devman->devices, &keys); + + for (size_t x = 0; x < count; x++) + { + DEVICE* cur = (DEVICE*)ListDictionary_GetItemValue(devman->devices, (void*)keys[x]); + + if (!cur) + continue; + + if (cur->type != type) + continue; + + device = cur; + break; + } + + free(keys); + ListDictionary_Unlock(devman->devices); + return device; +} + +static const char DRIVE_SERVICE_NAME[] = "drive"; +static const char PRINTER_SERVICE_NAME[] = "printer"; +static const char SMARTCARD_SERVICE_NAME[] = "smartcard"; +static const char SERIAL_SERVICE_NAME[] = "serial"; +static const char PARALLEL_SERVICE_NAME[] = "parallel"; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT devman_load_device_service(DEVMAN* devman, const RDPDR_DEVICE* device, rdpContext* rdpcontext) +{ + const char* ServiceName = NULL; + DEVICE_SERVICE_ENTRY_POINTS ep; + PDEVICE_SERVICE_ENTRY entry = NULL; + union + { + const RDPDR_DEVICE* cdp; + RDPDR_DEVICE* dp; + } devconv; + + devconv.cdp = device; + if (!devman || !device || !rdpcontext) + return ERROR_INVALID_PARAMETER; + + if (device->Type == RDPDR_DTYP_FILESYSTEM) + ServiceName = DRIVE_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_PRINT) + ServiceName = PRINTER_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_SMARTCARD) + ServiceName = SMARTCARD_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_SERIAL) + ServiceName = SERIAL_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_PARALLEL) + ServiceName = PARALLEL_SERVICE_NAME; + + if (!ServiceName) + { + WLog_INFO(TAG, "ServiceName %s did not match!", ServiceName); + return ERROR_INVALID_NAME; + } + + if (device->Name) + WLog_INFO(TAG, "Loading device service %s [%s] (static)", ServiceName, device->Name); + else + WLog_INFO(TAG, "Loading device service %s (static)", ServiceName); + + entry = (PDEVICE_SERVICE_ENTRY)freerdp_load_channel_addin_entry(ServiceName, NULL, + "DeviceServiceEntry", 0); + + if (!entry) + { + WLog_INFO(TAG, "freerdp_load_channel_addin_entry failed!"); + return ERROR_INTERNAL_ERROR; + } + + ep.devman = devman; + ep.RegisterDevice = devman_register_device; + ep.device = devconv.dp; + ep.rdpcontext = rdpcontext; + return entry(&ep); +} diff --git a/channels/rdpdr/client/devman.h b/channels/rdpdr/client/devman.h new file mode 100644 index 0000000..2e6019e --- /dev/null +++ b/channels/rdpdr/client/devman.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-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. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H + +#include "rdpdr_main.h" + +void devman_unregister_device(DEVMAN* devman, void* key); +UINT devman_load_device_service(DEVMAN* devman, const RDPDR_DEVICE* device, rdpContext* rdpcontext); +DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id); +DEVICE* devman_get_device_by_type(DEVMAN* devman, UINT32 type); + +DEVMAN* devman_new(rdpdrPlugin* rdpdr); +void devman_free(DEVMAN* devman); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H */ diff --git a/channels/rdpdr/client/irp.c b/channels/rdpdr/client/irp.c new file mode 100644 index 0000000..eeba80d --- /dev/null +++ b/channels/rdpdr/client/irp.c @@ -0,0 +1,165 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> +#include <winpr/stream.h> + +#include <freerdp/utils/rdpdr_utils.h> + +#include "rdpdr_main.h" +#include "devman.h" +#include "irp.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT irp_free(IRP* irp) +{ + if (!irp) + return CHANNEL_RC_OK; + + if (irp->input) + Stream_Release(irp->input); + if (irp->output) + Stream_Release(irp->output); + + winpr_aligned_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT irp_complete(IRP* irp) +{ + size_t pos = 0; + rdpdrPlugin* rdpdr = NULL; + UINT error = 0; + + WINPR_ASSERT(irp); + WINPR_ASSERT(irp->output); + WINPR_ASSERT(irp->devman); + + rdpdr = (rdpdrPlugin*)irp->devman->plugin; + WINPR_ASSERT(rdpdr); + + pos = Stream_GetPosition(irp->output); + Stream_SetPosition(irp->output, RDPDR_DEVICE_IO_RESPONSE_LENGTH - 4); + Stream_Write_UINT32(irp->output, irp->IoStatus); /* IoStatus (4 bytes) */ + Stream_SetPosition(irp->output, pos); + + error = rdpdr_send(rdpdr, irp->output); + irp->output = NULL; + + irp_free(irp); + return error; +} + +IRP* irp_new(DEVMAN* devman, wStreamPool* pool, wStream* s, wLog* log, UINT* error) +{ + IRP* irp = NULL; + DEVICE* device = NULL; + UINT32 DeviceId = 0; + + WINPR_ASSERT(devman); + WINPR_ASSERT(pool); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 20)) + { + if (error) + *error = ERROR_INVALID_DATA; + return NULL; + } + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + device = devman_get_device_by_id(devman, DeviceId); + + if (!device) + { + if (error) + *error = ERROR_DEV_NOT_EXIST; + + return NULL; + } + + irp = (IRP*)winpr_aligned_malloc(sizeof(IRP), MEMORY_ALLOCATION_ALIGNMENT); + + if (!irp) + { + WLog_Print(log, WLOG_ERROR, "_aligned_malloc failed!"); + if (error) + *error = CHANNEL_RC_NO_MEMORY; + return NULL; + } + + ZeroMemory(irp, sizeof(IRP)); + + Stream_Read_UINT32(s, irp->FileId); /* FileId (4 bytes) */ + Stream_Read_UINT32(s, irp->CompletionId); /* CompletionId (4 bytes) */ + Stream_Read_UINT32(s, irp->MajorFunction); /* MajorFunction (4 bytes) */ + Stream_Read_UINT32(s, irp->MinorFunction); /* MinorFunction (4 bytes) */ + + Stream_AddRef(s); + irp->input = s; + irp->device = device; + irp->devman = devman; + + irp->output = StreamPool_Take(pool, 256); + if (!irp->output) + { + WLog_Print(log, WLOG_ERROR, "Stream_New failed!"); + irp_free(irp); + if (error) + *error = CHANNEL_RC_NO_MEMORY; + return NULL; + } + + if (!rdpdr_write_iocompletion_header(irp->output, DeviceId, irp->CompletionId, 0)) + { + irp_free(irp); + if (error) + *error = CHANNEL_RC_NO_MEMORY; + return NULL; + } + + irp->Complete = irp_complete; + irp->Discard = irp_free; + + irp->thread = NULL; + irp->cancelled = FALSE; + + if (error) + *error = CHANNEL_RC_OK; + + return irp; +} diff --git a/channels/rdpdr/client/irp.h b/channels/rdpdr/client/irp.h new file mode 100644 index 0000000..0538295 --- /dev/null +++ b/channels/rdpdr/client/irp.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-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_CHANNEL_RDPDR_CLIENT_IRP_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H + +#include <winpr/wlog.h> +#include "rdpdr_main.h" + +IRP* irp_new(DEVMAN* devman, wStreamPool* pool, wStream* s, wLog* log, UINT* error); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H */ diff --git a/channels/rdpdr/client/rdpdr_capabilities.c b/channels/rdpdr/client/rdpdr_capabilities.c new file mode 100644 index 0000000..e094cc7 --- /dev/null +++ b/channels/rdpdr/client/rdpdr_capabilities.c @@ -0,0 +1,259 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015-2016 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2016 Armin Novak <armin.novak@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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> +#include <winpr/stream.h> + +#include <freerdp/utils/rdpdr_utils.h> + +#include "rdpdr_main.h" +#include "rdpdr_capabilities.h" + +#define RDPDR_CAPABILITY_HEADER_LENGTH 8 + +/* Output device direction general capability set */ +static void rdpdr_write_general_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH + 36, + GENERAL_CAPABILITY_VERSION_02 }; + + const UINT32 ioCode1 = rdpdr->clientIOCode1 & rdpdr->serverIOCode1; + const UINT32 ioCode2 = rdpdr->clientIOCode2 & rdpdr->serverIOCode2; + + rdpdr_write_capset_header(rdpdr->log, s, &header); + Stream_Write_UINT32(s, rdpdr->clientOsType); /* osType, ignored on receipt */ + Stream_Write_UINT32(s, rdpdr->clientOsVersion); /* osVersion, unused and must be set to zero */ + Stream_Write_UINT16(s, rdpdr->clientVersionMajor); /* protocolMajorVersion, must be set to 1 */ + Stream_Write_UINT16(s, rdpdr->clientVersionMinor); /* protocolMinorVersion */ + Stream_Write_UINT32(s, ioCode1); /* ioCode1 */ + Stream_Write_UINT32(s, ioCode2); /* ioCode2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, rdpdr->clientExtendedPDU); /* extendedPDU */ + Stream_Write_UINT32(s, rdpdr->clientExtraFlags1); /* extraFlags1 */ + Stream_Write_UINT32( + s, + rdpdr->clientExtraFlags2); /* extraFlags2, must be set to zero, reserved for future use */ + Stream_Write_UINT32( + s, rdpdr->clientSpecialTypeDeviceCap); /* SpecialTypeDeviceCap, number of special devices to + be redirected before logon */ +} + +/* Process device direction general capability set */ +static UINT rdpdr_process_general_capset(rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + + if (header->CapabilityLength != 36) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "CAP_GENERAL_TYPE::CapabilityLength expected 36, got %" PRIu32, + header->CapabilityLength); + return ERROR_INVALID_DATA; + } + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 36)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, rdpdr->serverOsType); /* osType, ignored on receipt */ + Stream_Read_UINT32(s, rdpdr->serverOsVersion); /* osVersion, unused and must be set to zero */ + Stream_Read_UINT16(s, rdpdr->serverVersionMajor); /* protocolMajorVersion, must be set to 1 */ + Stream_Read_UINT16(s, rdpdr->serverVersionMinor); /* protocolMinorVersion */ + Stream_Read_UINT32(s, rdpdr->serverIOCode1); /* ioCode1 */ + Stream_Read_UINT32( + s, rdpdr->serverIOCode2); /* ioCode2, must be set to zero, reserved for future use */ + Stream_Read_UINT32(s, rdpdr->serverExtendedPDU); /* extendedPDU */ + Stream_Read_UINT32(s, rdpdr->serverExtraFlags1); /* extraFlags1 */ + Stream_Read_UINT32( + s, + rdpdr->serverExtraFlags2); /* extraFlags2, must be set to zero, reserved for future use */ + Stream_Read_UINT32( + s, rdpdr->serverSpecialTypeDeviceCap); /* SpecialTypeDeviceCap, number of special devices to + be redirected before logon */ + return CHANNEL_RC_OK; +} + +/* Output printer direction capability set */ +static void rdpdr_write_printer_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + PRINT_CAPABILITY_VERSION_01 }; + rdpdr_write_capset_header(rdpdr->log, s, &header); +} + +/* Process printer direction capability set */ +static UINT rdpdr_process_printer_capset(rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +/* Output port redirection capability set */ +static void rdpdr_write_port_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + PORT_CAPABILITY_VERSION_01 }; + rdpdr_write_capset_header(rdpdr->log, s, &header); +} + +/* Process port redirection capability set */ +static UINT rdpdr_process_port_capset(rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +/* Output drive redirection capability set */ +static void rdpdr_write_drive_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + DRIVE_CAPABILITY_VERSION_02 }; + rdpdr_write_capset_header(rdpdr->log, s, &header); +} + +/* Process drive redirection capability set */ +static UINT rdpdr_process_drive_capset(rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +/* Output smart card redirection capability set */ +static void rdpdr_write_smartcard_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + SMARTCARD_CAPABILITY_VERSION_01 }; + rdpdr_write_capset_header(rdpdr->log, s, &header); +} + +/* Process smartcard redirection capability set */ +static UINT rdpdr_process_smartcard_capset(rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +UINT rdpdr_process_capability_request(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT status = CHANNEL_RC_OK; + UINT16 numCapabilities = 0; + + if (!rdpdr || !s) + return CHANNEL_RC_NULL_DATA; + + WINPR_ASSERT(rdpdr->state == RDPDR_CHANNEL_STATE_SERVER_CAPS); + rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_CLIENT_CAPS); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 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; + + switch (header.CapabilityType) + { + case CAP_GENERAL_TYPE: + status = rdpdr_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: + + break; + } + + if (status != CHANNEL_RC_OK) + return status; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpdr_send_capability_response(rdpdrPlugin* rdpdr) +{ + wStream* s = NULL; + + WINPR_ASSERT(rdpdr); + s = StreamPool_Take(rdpdr->pool, 256); + + if (!s) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); + Stream_Write_UINT16(s, PAKID_CORE_CLIENT_CAPABILITY); + Stream_Write_UINT16(s, 5); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + rdpdr_write_general_capset(rdpdr, s); + rdpdr_write_printer_capset(rdpdr, s); + rdpdr_write_port_capset(rdpdr, s); + rdpdr_write_drive_capset(rdpdr, s); + rdpdr_write_smartcard_capset(rdpdr, s); + return rdpdr_send(rdpdr, s); +} diff --git a/channels/rdpdr/client/rdpdr_capabilities.h b/channels/rdpdr/client/rdpdr_capabilities.h new file mode 100644 index 0000000..d4e1ecb --- /dev/null +++ b/channels/rdpdr/client/rdpdr_capabilities.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-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. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H + +#include "rdpdr_main.h" + +UINT rdpdr_process_capability_request(rdpdrPlugin* rdpdr, wStream* s); +UINT rdpdr_send_capability_response(rdpdrPlugin* rdpdr); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H */ diff --git a/channels/rdpdr/client/rdpdr_main.c b/channels/rdpdr/client/rdpdr_main.c new file mode 100644 index 0000000..53f5011 --- /dev/null +++ b/channels/rdpdr/client/rdpdr_main.c @@ -0,0 +1,2342 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015-2016 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.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 <winpr/crt.h> +#include <winpr/sysinfo.h> +#include <winpr/assert.h> +#include <winpr/stream.h> + +#include <winpr/print.h> +#include <winpr/sspicli.h> + +#include <freerdp/types.h> +#include <freerdp/freerdp.h> +#include <freerdp/constants.h> +#include <freerdp/channels/log.h> +#include <freerdp/channels/rdpdr.h> +#include <freerdp/utils/rdpdr_utils.h> + +#ifdef _WIN32 +#include <windows.h> +#include <dbt.h> +#else +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#endif + +#ifdef __MACOSX__ +#include <CoreFoundation/CoreFoundation.h> +#include <stdio.h> +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#endif + +#include "rdpdr_capabilities.h" + +#include "devman.h" +#include "irp.h" + +#include "rdpdr_main.h" + +#define TAG CHANNELS_TAG("rdpdr.client") + +/* IMPORTANT: Keep in sync with DRIVE_DEVICE */ +typedef struct +{ + DEVICE device; + WCHAR* path; + BOOL automount; +} DEVICE_DRIVE_EXT; + +static const char* rdpdr_state_str(enum RDPDR_CHANNEL_STATE state) +{ + switch (state) + { + case RDPDR_CHANNEL_STATE_INITIAL: + return "RDPDR_CHANNEL_STATE_INITIAL"; + case RDPDR_CHANNEL_STATE_ANNOUNCE: + return "RDPDR_CHANNEL_STATE_ANNOUNCE"; + case RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY: + return "RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY"; + case RDPDR_CHANNEL_STATE_NAME_REQUEST: + return "RDPDR_CHANNEL_STATE_NAME_REQUEST"; + case RDPDR_CHANNEL_STATE_SERVER_CAPS: + return "RDPDR_CHANNEL_STATE_SERVER_CAPS"; + case RDPDR_CHANNEL_STATE_CLIENT_CAPS: + return "RDPDR_CHANNEL_STATE_CLIENT_CAPS"; + case RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM: + return "RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM"; + case RDPDR_CHANNEL_STATE_READY: + return "RDPDR_CHANNEL_STATE_READY"; + case RDPDR_CHANNEL_STATE_USER_LOGGEDON: + return "RDPDR_CHANNEL_STATE_USER_LOGGEDON"; + default: + return "RDPDR_CHANNEL_STATE_UNKNOWN"; + } +} + +static const char* rdpdr_device_type_string(UINT32 type) +{ + switch (type) + { + case RDPDR_DTYP_SERIAL: + return "serial"; + case RDPDR_DTYP_PRINT: + return "printer"; + case RDPDR_DTYP_FILESYSTEM: + return "drive"; + case RDPDR_DTYP_SMARTCARD: + return "smartcard"; + case RDPDR_DTYP_PARALLEL: + return "parallel"; + default: + return "UNKNOWN"; + } +} + +static const char* support_str(BOOL val) +{ + if (val) + return "supported"; + return "not found"; +} + +static const char* rdpdr_caps_pdu_str(UINT32 flag) +{ + switch (flag) + { + case RDPDR_DEVICE_REMOVE_PDUS: + return "RDPDR_USER_LOGGEDON_PDU"; + case RDPDR_CLIENT_DISPLAY_NAME_PDU: + return "RDPDR_CLIENT_DISPLAY_NAME_PDU"; + case RDPDR_USER_LOGGEDON_PDU: + return "RDPDR_USER_LOGGEDON_PDU"; + default: + return "RDPDR_UNKNONW"; + } +} + +static BOOL rdpdr_check_extended_pdu_flag(rdpdrPlugin* rdpdr, UINT32 flag) +{ + WINPR_ASSERT(rdpdr); + + const BOOL client = (rdpdr->clientExtendedPDU & flag) != 0; + const BOOL server = (rdpdr->serverExtendedPDU & flag) != 0; + + if (!client || !server) + { + WLog_Print(rdpdr->log, WLOG_WARN, "Checking ExtendedPDU::%s, client %s, server %s", + rdpdr_caps_pdu_str(flag), support_str(client), support_str(server)); + return FALSE; + } + return TRUE; +} + +BOOL rdpdr_state_advance(rdpdrPlugin* rdpdr, enum RDPDR_CHANNEL_STATE next) +{ + WINPR_ASSERT(rdpdr); + + if (next != rdpdr->state) + WLog_Print(rdpdr->log, WLOG_DEBUG, "[RDPDR] transition from %s to %s", + rdpdr_state_str(rdpdr->state), rdpdr_state_str(next)); + rdpdr->state = next; + return TRUE; +} + +static BOOL device_foreach(rdpdrPlugin* rdpdr, BOOL abortOnFail, + BOOL (*fkt)(ULONG_PTR key, void* element, void* data), void* data) +{ + BOOL rc = TRUE; + ULONG_PTR* keys = NULL; + + ListDictionary_Lock(rdpdr->devman->devices); + const size_t count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + for (size_t x = 0; x < count; x++) + { + void* element = ListDictionary_GetItemValue(rdpdr->devman->devices, (void*)keys[x]); + if (!fkt(keys[x], element, data)) + { + rc = FALSE; + if (abortOnFail) + break; + } + } + free(keys); + ListDictionary_Unlock(rdpdr->devman->devices); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_try_send_device_list_announce_request(rdpdrPlugin* rdpdr); + +static BOOL rdpdr_load_drive(rdpdrPlugin* rdpdr, const char* name, const char* path, BOOL automount) +{ + UINT rc = ERROR_INTERNAL_ERROR; + union + { + RDPDR_DRIVE* drive; + RDPDR_DEVICE* device; + } drive; + const char* args[] = { name, path, automount ? NULL : name }; + + drive.device = freerdp_device_new(RDPDR_DTYP_FILESYSTEM, ARRAYSIZE(args), args); + if (!drive.device) + goto fail; + + rc = devman_load_device_service(rdpdr->devman, drive.device, rdpdr->rdpcontext); + if (rc != CHANNEL_RC_OK) + goto fail; + +fail: + freerdp_device_free(drive.device); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_device_list_remove_request(rdpdrPlugin* rdpdr, UINT32 count, UINT32 ids[]) +{ + wStream* s = NULL; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(ids || (count == 0)); + + if (count == 0) + return CHANNEL_RC_OK; + + if (!rdpdr_check_extended_pdu_flag(rdpdr, RDPDR_DEVICE_REMOVE_PDUS)) + return CHANNEL_RC_OK; + + s = StreamPool_Take(rdpdr->pool, count * sizeof(UINT32) + 8); + + if (!s) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); + Stream_Write_UINT16(s, PAKID_CORE_DEVICELIST_REMOVE); + Stream_Write_UINT32(s, count); + + for (UINT32 i = 0; i < count; i++) + Stream_Write_UINT32(s, ids[i]); + + Stream_SealLength(s); + return rdpdr_send(rdpdr, s); +} + +#if defined(_UWP) || defined(__IOS__) + +static void first_hotplug(rdpdrPlugin* rdpdr) +{ +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + return CHANNEL_RC_OK; +} + +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + return CHANNEL_RC_OK; +} + +#elif defined(_WIN32) + +static BOOL check_path(const char* path) +{ + UINT type = GetDriveTypeA(path); + + if (!(type == DRIVE_FIXED || type == DRIVE_REMOVABLE || type == DRIVE_CDROM || + type == DRIVE_REMOTE)) + return FALSE; + + return GetVolumeInformationA(path, NULL, 0, NULL, NULL, NULL, NULL, 0); +} + +static void first_hotplug(rdpdrPlugin* rdpdr) +{ + DWORD unitmask = GetLogicalDrives(); + + for (size_t i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + char drive_path[] = { 'c', ':', '\\', '\0' }; + char drive_name[] = { 'c', '\0' }; + drive_path[0] = 'A' + (char)i; + drive_name[0] = 'A' + (char)i; + + if (check_path(drive_path)) + { + rdpdr_load_drive(rdpdr, drive_name, drive_path, TRUE); + } + } + + unitmask = unitmask >> 1; + } +} + +static LRESULT CALLBACK hotplug_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + rdpdrPlugin* rdpdr; + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + UINT error; + rdpdr = (rdpdrPlugin*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + + switch (Msg) + { + case WM_DEVICECHANGE: + switch (wParam) + { + case DBT_DEVICEARRIVAL: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + { + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; + DWORD unitmask = lpdbv->dbcv_unitmask; + + for (int i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + char drive_path[] = { 'c', ':', '/', '\0' }; + char drive_name[] = { 'c', '\0' }; + drive_path[0] = 'A' + (char)i; + drive_name[0] = 'A' + (char)i; + + if (check_path(drive_path)) + { + rdpdr_load_drive(rdpdr, drive_name, drive_path, TRUE); + rdpdr_try_send_device_list_announce_request(rdpdr); + } + } + + unitmask = unitmask >> 1; + } + } + + break; + + case DBT_DEVICEREMOVECOMPLETE: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + { + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; + DWORD unitmask = lpdbv->dbcv_unitmask; + int count; + char drive_name_upper, drive_name_lower; + ULONG_PTR* keys = NULL; + DEVICE_DRIVE_EXT* device_ext; + UINT32 ids[1]; + + for (int i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + drive_name_upper = 'A' + i; + drive_name_lower = 'a' + i; + count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + + for (int j = 0; j < count; j++) + { + device_ext = (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue( + rdpdr->devman->devices, (void*)keys[j]); + + if (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) + continue; + + if (device_ext->path[0] == drive_name_upper || + device_ext->path[0] == drive_name_lower) + { + if (device_ext->automount) + { + devman_unregister_device(rdpdr->devman, (void*)keys[j]); + ids[0] = keys[j]; + + if ((error = rdpdr_send_device_list_remove_request( + rdpdr, 1, ids))) + { + // dont end on error, just report ? + WLog_Print( + rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_remove_request failed " + "with error %" PRIu32 "!", + error); + } + + break; + } + } + } + + free(keys); + } + + unitmask = unitmask >> 1; + } + } + + break; + + default: + break; + } + + break; + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return DefWindowProc(hWnd, Msg, wParam, lParam); +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr; + WNDCLASSEX wnd_cls; + HWND hwnd; + MSG msg; + BOOL bRet; + DEV_BROADCAST_HANDLE NotificationFilter; + HDEVNOTIFY hDevNotify; + rdpdr = (rdpdrPlugin*)arg; + /* init windows class */ + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.style = CS_HREDRAW | CS_VREDRAW; + wnd_cls.lpfnWndProc = hotplug_proc; + wnd_cls.cbClsExtra = 0; + wnd_cls.cbWndExtra = 0; + wnd_cls.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wnd_cls.hCursor = NULL; + wnd_cls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wnd_cls.lpszMenuName = NULL; + wnd_cls.lpszClassName = L"DRIVE_HOTPLUG"; + wnd_cls.hInstance = NULL; + wnd_cls.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + RegisterClassEx(&wnd_cls); + /* create window */ + hwnd = CreateWindowEx(0, L"DRIVE_HOTPLUG", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)rdpdr); + rdpdr->hotplug_wnd = hwnd; + /* register device interface to hwnd */ + NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE); + NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE; + hDevNotify = RegisterDeviceNotification(hwnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); + + /* message loop */ + while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0) + { + if (bRet == -1) + { + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + UnregisterDeviceNotification(hDevNotify); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + UINT error = CHANNEL_RC_OK; + + if (rdpdr->hotplug_wnd && !PostMessage(rdpdr->hotplug_wnd, WM_QUIT, 0, 0)) + { + error = GetLastError(); + WLog_Print(rdpdr->log, WLOG_ERROR, "PostMessage failed with error %" PRIu32 "", error); + } + + return error; +} + +#elif defined(__MACOSX__) + +#define MAX_USB_DEVICES 100 + +typedef struct +{ + char* path; + BOOL to_add; +} hotplug_dev; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT handle_hotplug(rdpdrPlugin* rdpdr) +{ + struct dirent* pDirent; + DIR* pDir; + char fullpath[PATH_MAX]; + char* szdir = (char*)"/Volumes"; + struct stat buf; + hotplug_dev dev_array[MAX_USB_DEVICES]; + int count; + DEVICE_DRIVE_EXT* device_ext; + ULONG_PTR* keys = NULL; + int size = 0; + UINT error; + UINT32 ids[1]; + pDir = opendir(szdir); + + if (pDir == NULL) + { + printf("Cannot open directory\n"); + return ERROR_OPEN_FAILED; + } + + while ((pDirent = readdir(pDir)) != NULL) + { + if (pDirent->d_name[0] != '.') + { + sprintf_s(fullpath, ARRAYSIZE(fullpath), "%s/%s", szdir, pDirent->d_name); + if (stat(fullpath, &buf) != 0) + continue; + + if (S_ISDIR(buf.st_mode)) + { + dev_array[size].path = _strdup(fullpath); + + if (!dev_array[size].path) + { + closedir(pDir); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + + dev_array[size++].to_add = TRUE; + } + } + } + + closedir(pDir); + /* delete removed devices */ + count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + + for (size_t j = 0; j < count; j++) + { + char* path = NULL; + BOOL dev_found = FALSE; + device_ext = + (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue(rdpdr->devman->devices, (void*)keys[j]); + + if (!device_ext || !device_ext->automount) + continue; + + if (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) + continue; + + if (device_ext->path == NULL) + continue; + + path = ConvertWCharToUtf8Alloc(device_ext->path, NULL); + if (!path) + continue; + + /* not plugable device */ + if (strstr(path, "/Volumes/") == NULL) + { + free(path); + continue; + } + + for (size_t i = 0; i < size; i++) + { + if (strstr(path, dev_array[i].path) != NULL) + { + dev_found = TRUE; + dev_array[i].to_add = FALSE; + break; + } + } + + free(path); + + if (!dev_found) + { + devman_unregister_device(rdpdr->devman, (void*)keys[j]); + ids[0] = keys[j]; + + if ((error = rdpdr_send_device_list_remove_request(rdpdr, 1, ids))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_remove_request failed with error %" PRIu32 "!", + error); + goto cleanup; + } + } + } + + /* add new devices */ + for (size_t i = 0; i < size; i++) + { + const hotplug_dev* dev = &dev_array[i]; + if (dev->to_add) + { + const char* path = dev->path; + const char* name = strrchr(path, '/') + 1; + error = rdpdr_load_drive(rdpdr, name, path, TRUE); + if (error) + goto cleanup; + } + } + +cleanup: + free(keys); + + for (size_t i = 0; i < size; i++) + free(dev_array[i].path); + + return error; +} + +static void drive_hotplug_fsevent_callback(ConstFSEventStreamRef streamRef, + void* clientCallBackInfo, size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + rdpdrPlugin* rdpdr; + UINT error; + char** paths = (char**)eventPaths; + rdpdr = (rdpdrPlugin*)clientCallBackInfo; + + for (size_t i = 0; i < numEvents; i++) + { + if (strcmp(paths[i], "/Volumes/") == 0) + { + if ((error = handle_hotplug(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!", + error); + } + else + rdpdr_try_send_device_list_announce_request(rdpdr); + + return; + } + } +} + +static void first_hotplug(rdpdrPlugin* rdpdr) +{ + UINT error; + + if ((error = handle_hotplug(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!", error); + } +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr; + FSEventStreamRef fsev; + rdpdr = (rdpdrPlugin*)arg; + CFStringRef path = CFSTR("/Volumes/"); + CFArrayRef pathsToWatch = CFArrayCreate(kCFAllocatorMalloc, (const void**)&path, 1, NULL); + FSEventStreamContext ctx = { 0 }; + + ctx.info = arg; + + WINPR_ASSERT(!rdpdr->stopEvent); + rdpdr->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!rdpdr->stopEvent) + goto out; + + fsev = + FSEventStreamCreate(kCFAllocatorMalloc, drive_hotplug_fsevent_callback, &ctx, pathsToWatch, + kFSEventStreamEventIdSinceNow, 1, kFSEventStreamCreateFlagNone); + + rdpdr->runLoop = CFRunLoopGetCurrent(); + FSEventStreamScheduleWithRunLoop(fsev, rdpdr->runLoop, kCFRunLoopDefaultMode); + FSEventStreamStart(fsev); + CFRunLoopRun(); + FSEventStreamStop(fsev); + FSEventStreamRelease(fsev); +out: + if (rdpdr->stopEvent) + { + CloseHandle(rdpdr->stopEvent); + rdpdr->stopEvent = NULL; + } + ExitThread(CHANNEL_RC_OK); + return CHANNEL_RC_OK; +} + +#else + +static const char* automountLocations[] = { "/run/user/%lu/gvfs", "/run/media/%s", "/media/%s", + "/media", "/mnt" }; + +static BOOL isAutomountLocation(const char* path) +{ + const size_t nrLocations = sizeof(automountLocations) / sizeof(automountLocations[0]); + char buffer[MAX_PATH] = { 0 }; + uid_t uid = getuid(); + char uname[MAX_PATH] = { 0 }; + ULONG size = sizeof(uname) - 1; + + if (!GetUserNameExA(NameSamCompatible, uname, &size)) + return FALSE; + + if (!path) + return FALSE; + + for (size_t x = 0; x < nrLocations; x++) + { + const char* location = automountLocations[x]; + size_t length = 0; + + if (strstr(location, "%lu")) + snprintf(buffer, sizeof(buffer), location, (unsigned long)uid); + else if (strstr(location, "%s")) + snprintf(buffer, sizeof(buffer), location, uname); + else + snprintf(buffer, sizeof(buffer), "%s", location); + + length = strnlen(buffer, sizeof(buffer)); + + if (strncmp(buffer, path, length) == 0) + { + const char* rest = &path[length]; + + /* Only consider mount locations with max depth of 1 below the + * base path or the base path itself. */ + if (*rest == '\0') + return TRUE; + else if (*rest == '/') + { + const char* token = strstr(&rest[1], "/"); + + if (!token || (token[1] == '\0')) + return TRUE; + } + } + } + + return FALSE; +} + +#define MAX_USB_DEVICES 100 + +typedef struct +{ + char* path; + BOOL to_add; +} hotplug_dev; + +static void handle_mountpoint(hotplug_dev* dev_array, size_t* size, const char* mountpoint) +{ + if (!mountpoint) + return; + /* copy hotpluged device mount point to the dev_array */ + if (isAutomountLocation(mountpoint) && (*size < MAX_USB_DEVICES)) + { + dev_array[*size].path = _strdup(mountpoint); + dev_array[*size].to_add = TRUE; + (*size)++; + } +} + +#ifdef __sun +#include <sys/mnttab.h> +static UINT handle_platform_mounts_sun(wLog* log, hotplug_dev* dev_array, size_t* size) +{ + FILE* f; + struct mnttab ent; + f = winpr_fopen("/etc/mnttab", "r"); + if (f == NULL) + { + WLog_Print(log, WLOG_ERROR, "fopen failed!"); + return ERROR_OPEN_FAILED; + } + while (getmntent(f, &ent) == 0) + { + handle_mountpoint(dev_array, size, ent.mnt_mountp); + } + fclose(f); + return ERROR_SUCCESS; +} +#endif + +#if defined(__FreeBSD__) || defined(__OpenBSD__) +#include <sys/mount.h> +static UINT handle_platform_mounts_bsd(wLog* log, hotplug_dev* dev_array, size_t* size) +{ + int mntsize; + struct statfs* mntbuf = NULL; + + mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); + if (!mntsize) + { + /* TODO: handle 'errno' */ + WLog_Print(log, WLOG_ERROR, "getmntinfo failed!"); + return ERROR_OPEN_FAILED; + } + for (size_t idx = 0; idx < (size_t)mntsize; idx++) + { + handle_mountpoint(dev_array, size, mntbuf[idx].f_mntonname); + } + free(mntbuf); + return ERROR_SUCCESS; +} +#endif + +#if defined(__LINUX__) || defined(__linux__) +#include <mntent.h> +static UINT handle_platform_mounts_linux(wLog* log, hotplug_dev* dev_array, size_t* size) +{ + FILE* f = NULL; + struct mntent* ent = NULL; + f = winpr_fopen("/proc/mounts", "r"); + if (f == NULL) + { + WLog_Print(log, WLOG_ERROR, "fopen failed!"); + return ERROR_OPEN_FAILED; + } + while ((ent = getmntent(f)) != NULL) + { + handle_mountpoint(dev_array, size, ent->mnt_dir); + } + fclose(f); + return ERROR_SUCCESS; +} +#endif + +static UINT handle_platform_mounts(wLog* log, hotplug_dev* dev_array, size_t* size) +{ +#ifdef __sun + return handle_platform_mounts_sun(log, dev_array, size); +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + return handle_platform_mounts_bsd(log, dev_array, size); +#elif defined(__LINUX__) || defined(__linux__) + return handle_platform_mounts_linux(log, dev_array, size); +#endif + return ERROR_CALL_NOT_IMPLEMENTED; +} + +static BOOL device_not_plugged(ULONG_PTR key, void* element, void* data) +{ + const WCHAR* path = (const WCHAR*)data; + DEVICE_DRIVE_EXT* device_ext = (DEVICE_DRIVE_EXT*)element; + + WINPR_UNUSED(key); + WINPR_ASSERT(path); + + if (!device_ext || (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) || !device_ext->path) + return TRUE; + if (_wcscmp(device_ext->path, path) != 0) + return TRUE; + return FALSE; +} + +static BOOL device_already_plugged(rdpdrPlugin* rdpdr, const hotplug_dev* device) +{ + BOOL rc = FALSE; + WCHAR* path = NULL; + + if (!rdpdr || !device) + return TRUE; + if (!device->to_add) + return TRUE; + + WINPR_ASSERT(rdpdr->devman); + WINPR_ASSERT(device->path); + + path = ConvertUtf8ToWCharAlloc(device->path, NULL); + if (!path) + return TRUE; + + rc = device_foreach(rdpdr, TRUE, device_not_plugged, path); + free(path); + return !rc; +} + +struct hotplug_delete_arg +{ + hotplug_dev* dev_array; + size_t dev_array_size; + rdpdrPlugin* rdpdr; +}; + +static BOOL hotplug_delete_foreach(ULONG_PTR key, void* element, void* data) +{ + char* path = NULL; + BOOL dev_found = FALSE; + struct hotplug_delete_arg* arg = (struct hotplug_delete_arg*)data; + DEVICE_DRIVE_EXT* device_ext = (DEVICE_DRIVE_EXT*)element; + + WINPR_ASSERT(arg); + WINPR_ASSERT(arg->rdpdr); + WINPR_ASSERT(arg->dev_array || (arg->dev_array_size == 0)); + + if (!device_ext || (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) || !device_ext->path || + !device_ext->automount) + return TRUE; + + WINPR_ASSERT(device_ext->path); + path = ConvertWCharToUtf8Alloc(device_ext->path, NULL); + if (!path) + return FALSE; + + /* not plugable device */ + if (isAutomountLocation(path)) + { + for (size_t i = 0; i < arg->dev_array_size; i++) + { + hotplug_dev* cur = &arg->dev_array[i]; + if (cur->path && strstr(path, cur->path) != NULL) + { + dev_found = TRUE; + cur->to_add = FALSE; + break; + } + } + } + + free(path); + + if (!dev_found) + { + UINT error = 0; + UINT32 ids[1] = { key }; + + WINPR_ASSERT(arg->rdpdr->devman); + devman_unregister_device(arg->rdpdr->devman, (void*)key); + WINPR_ASSERT(key <= UINT32_MAX); + + error = rdpdr_send_device_list_remove_request(arg->rdpdr, 1, ids); + if (error) + { + WLog_Print(arg->rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_remove_request failed with error %" PRIu32 "!", + error); + return FALSE; + } + } + + return TRUE; +} + +static UINT handle_hotplug(rdpdrPlugin* rdpdr) +{ + hotplug_dev dev_array[MAX_USB_DEVICES] = { 0 }; + size_t size = 0; + UINT error = ERROR_SUCCESS; + struct hotplug_delete_arg arg = { dev_array, ARRAYSIZE(dev_array), rdpdr }; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->devman); + + error = handle_platform_mounts(rdpdr->log, dev_array, &size); + + /* delete removed devices */ + /* Ignore result */ device_foreach(rdpdr, FALSE, hotplug_delete_foreach, &arg); + + /* add new devices */ + for (size_t i = 0; i < size; i++) + { + hotplug_dev* cur = &dev_array[i]; + if (!device_already_plugged(rdpdr, cur)) + { + const char* path = cur->path; + const char* name = strrchr(path, '/') + 1; + + rdpdr_load_drive(rdpdr, name, path, TRUE); + error = ERROR_DISK_CHANGE; + } + } + + for (size_t i = 0; i < size; i++) + free(dev_array[i].path); + + return error; +} + +static void first_hotplug(rdpdrPlugin* rdpdr) +{ + UINT error = 0; + + WINPR_ASSERT(rdpdr); + if ((error = handle_hotplug(rdpdr))) + { + switch (error) + { + case ERROR_DISK_CHANGE: + case CHANNEL_RC_OK: + case ERROR_OPEN_FAILED: + case ERROR_CALL_NOT_IMPLEMENTED: + break; + default: + WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!", + error); + break; + } + } +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr = NULL; + UINT error = 0; + rdpdr = (rdpdrPlugin*)arg; + + WINPR_ASSERT(rdpdr); + + WINPR_ASSERT(!rdpdr->stopEvent); + rdpdr->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!rdpdr->stopEvent) + goto out; + + while (WaitForSingleObject(rdpdr->stopEvent, 1000) == WAIT_TIMEOUT) + { + error = handle_hotplug(rdpdr); + switch (error) + { + case ERROR_DISK_CHANGE: + rdpdr_try_send_device_list_announce_request(rdpdr); + break; + case CHANNEL_RC_OK: + case ERROR_OPEN_FAILED: + case ERROR_CALL_NOT_IMPLEMENTED: + break; + default: + WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!", + error); + goto out; + } + } + +out: + error = GetLastError(); + if (error && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, "reported an error"); + + if (rdpdr->stopEvent) + { + CloseHandle(rdpdr->stopEvent); + rdpdr->stopEvent = NULL; + } + + ExitThread(error); + return error; +} + +#endif + +#if !defined(_WIN32) && !defined(__IOS__) +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + UINT error = 0; + + WINPR_ASSERT(rdpdr); + + if (rdpdr->hotplugThread) + { +#if !defined(_WIN32) + if (rdpdr->stopEvent) + SetEvent(rdpdr->stopEvent); +#endif +#ifdef __MACOSX__ + CFRunLoopStop(rdpdr->runLoop); +#endif + + if (WaitForSingleObject(rdpdr->hotplugThread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(rdpdr->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!", + error); + return error; + } + + CloseHandle(rdpdr->hotplugThread); + rdpdr->hotplugThread = NULL; + } + + return CHANNEL_RC_OK; +} + +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_connect(rdpdrPlugin* rdpdr) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(rdpdr); + + rdpdr->devman = devman_new(rdpdr); + + if (!rdpdr->devman) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "devman_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + WINPR_ASSERT(rdpdr->rdpcontext); + + rdpSettings* settings = rdpdr->rdpcontext->settings; + WINPR_ASSERT(settings); + + rdpdr->ignoreInvalidDevices = freerdp_settings_get_bool(settings, FreeRDP_IgnoreInvalidDevices); + + const char* name = freerdp_settings_get_string(settings, FreeRDP_ClientHostname); + if (!name) + name = freerdp_settings_get_string(settings, FreeRDP_ComputerName); + strncpy(rdpdr->computerName, name, sizeof(rdpdr->computerName) - 1); + + for (UINT32 index = 0; index < freerdp_settings_get_uint32(settings, FreeRDP_DeviceCount); + index++) + { + const RDPDR_DEVICE* device = + freerdp_settings_get_pointer_array(settings, FreeRDP_DeviceArray, index); + + if (device->Type == RDPDR_DTYP_FILESYSTEM) + { + const char DynamicDrives[] = "DynamicDrives"; + const RDPDR_DRIVE* drive = (const RDPDR_DRIVE*)device; + BOOL hotplugAll = strncmp(drive->Path, "*", 2) == 0; + BOOL hotplugLater = strncmp(drive->Path, DynamicDrives, sizeof(DynamicDrives)) == 0; + + if (drive->Path && (hotplugAll || hotplugLater)) + { + if (!rdpdr->async) + { + WLog_Print(rdpdr->log, WLOG_WARN, + "Drive hotplug is not supported in synchronous mode!"); + continue; + } + + if (hotplugAll) + first_hotplug(rdpdr); + + /* There might be multiple hotplug related device entries. + * Ensure the thread is only started once + */ + if (!rdpdr->hotplugThread) + { + rdpdr->hotplugThread = + CreateThread(NULL, 0, drive_hotplug_thread_func, rdpdr, 0, NULL); + if (!rdpdr->hotplugThread) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "CreateThread failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + continue; + } + } + + if ((error = devman_load_device_service(rdpdr->devman, device, rdpdr->rdpcontext))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "devman_load_device_service failed with error %" PRIu32 "!", error); + return error; + } + } + + return error; +} + +static UINT rdpdr_process_server_announce_request(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, rdpdr->serverVersionMajor); + Stream_Read_UINT16(s, rdpdr->serverVersionMinor); + Stream_Read_UINT32(s, rdpdr->clientID); + rdpdr->sequenceId++; + + rdpdr->clientVersionMajor = MIN(RDPDR_VERSION_MAJOR, rdpdr->serverVersionMajor); + rdpdr->clientVersionMinor = MIN(RDPDR_VERSION_MINOR_RDP10X, rdpdr->serverVersionMinor); + WLog_Print(rdpdr->log, WLOG_DEBUG, + "[rdpdr] server announces version %" PRIu32 ".%" PRIu32 ", client uses %" PRIu32 + ".%" PRIu32, + rdpdr->serverVersionMajor, rdpdr->serverVersionMinor, rdpdr->clientVersionMajor, + rdpdr->clientVersionMinor); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_client_announce_reply(rdpdrPlugin* rdpdr) +{ + wStream* s = NULL; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->state == RDPDR_CHANNEL_STATE_ANNOUNCE); + rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY); + + s = StreamPool_Take(rdpdr->pool, 12); + + if (!s) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_CLIENTID_CONFIRM); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, rdpdr->clientVersionMajor); + Stream_Write_UINT16(s, rdpdr->clientVersionMinor); + Stream_Write_UINT32(s, (UINT32)rdpdr->clientID); + return rdpdr_send(rdpdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_client_name_request(rdpdrPlugin* rdpdr) +{ + wStream* s = NULL; + WCHAR* computerNameW = NULL; + size_t computerNameLenW = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->state == RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY); + rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_NAME_REQUEST); + + if (!rdpdr->computerName[0]) + { + DWORD size = sizeof(rdpdr->computerName) - 1; + GetComputerNameA(rdpdr->computerName, &size); + } + + WINPR_ASSERT(rdpdr->computerName); + computerNameW = ConvertUtf8ToWCharAlloc(rdpdr->computerName, &computerNameLenW); + computerNameLenW *= sizeof(WCHAR); + WINPR_ASSERT(computerNameLenW >= 0); + + if (computerNameLenW > 0) + computerNameLenW += sizeof(WCHAR); // also write '\0' + + s = StreamPool_Take(rdpdr->pool, 16U + computerNameLenW); + + if (!s) + { + free(computerNameW); + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_CLIENT_NAME); /* PacketId (2 bytes) */ + Stream_Write_UINT32(s, 1); /* unicodeFlag, 0 for ASCII and 1 for Unicode */ + Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */ + Stream_Write_UINT32(s, + (UINT32)computerNameLenW); /* computerNameLen, including null terminator */ + Stream_Write(s, computerNameW, (size_t)computerNameLenW); + free(computerNameW); + return rdpdr_send(rdpdr, s); +} + +static UINT rdpdr_process_server_clientid_confirm(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 versionMajor = 0; + UINT16 versionMinor = 0; + UINT32 clientID = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + Stream_Read_UINT32(s, clientID); + + if (versionMajor != rdpdr->clientVersionMajor || versionMinor != rdpdr->clientVersionMinor) + { + WLog_Print(rdpdr->log, WLOG_WARN, + "[rdpdr] server announced version %" PRIu32 ".%" PRIu32 ", client uses %" PRIu32 + ".%" PRIu32 " but clientid confirm requests version %" PRIu32 ".%" PRIu32, + rdpdr->serverVersionMajor, rdpdr->serverVersionMinor, rdpdr->clientVersionMajor, + rdpdr->clientVersionMinor, versionMajor, versionMinor); + rdpdr->clientVersionMajor = versionMajor; + rdpdr->clientVersionMinor = versionMinor; + } + + if (clientID != rdpdr->clientID) + rdpdr->clientID = clientID; + + return CHANNEL_RC_OK; +} + +struct device_announce_arg +{ + rdpdrPlugin* rdpdr; + wStream* s; + BOOL userLoggedOn; + UINT32 count; +}; + +static BOOL device_announce(ULONG_PTR key, void* element, void* data) +{ + struct device_announce_arg* arg = data; + rdpdrPlugin* rdpdr = NULL; + DEVICE* device = (DEVICE*)element; + + WINPR_UNUSED(key); + + WINPR_ASSERT(arg); + WINPR_ASSERT(device); + WINPR_ASSERT(arg->rdpdr); + WINPR_ASSERT(arg->s); + + rdpdr = arg->rdpdr; + + /** + * 1. versionMinor 0x0005 doesn't send PAKID_CORE_USER_LOGGEDON + * so all devices should be sent regardless of user_loggedon + * 2. smartcard devices should be always sent + * 3. other devices are sent only after user_loggedon + */ + + if ((rdpdr->clientVersionMinor == RDPDR_VERSION_MINOR_RDP51) || + (device->type == RDPDR_DTYP_SMARTCARD) || arg->userLoggedOn) + { + size_t data_len = (device->data == NULL ? 0 : Stream_GetPosition(device->data)); + + if (!Stream_EnsureRemainingCapacity(arg->s, 20 + data_len)) + { + Stream_Release(arg->s); + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + return FALSE; + } + + Stream_Write_UINT32(arg->s, device->type); /* deviceType */ + Stream_Write_UINT32(arg->s, device->id); /* deviceID */ + strncpy(Stream_Pointer(arg->s), device->name, 8); + + for (size_t i = 0; i < 8; i++) + { + BYTE c = 0; + Stream_Peek_UINT8(arg->s, c); + + if (c > 0x7F) + Stream_Write_UINT8(arg->s, '_'); + else + Stream_Seek_UINT8(arg->s); + } + + WINPR_ASSERT(data_len <= UINT32_MAX); + Stream_Write_UINT32(arg->s, (UINT32)data_len); + + if (data_len > 0) + Stream_Write(arg->s, Stream_Buffer(device->data), data_len); + + arg->count++; + WLog_Print(rdpdr->log, WLOG_INFO, + "registered [%09s] device #%" PRIu32 ": %s (type=%" PRIu32 " id=%" PRIu32 ")", + rdpdr_device_type_string(device->type), arg->count, device->name, device->type, + device->id); + } + return TRUE; +} + +static UINT rdpdr_send_device_list_announce_request(rdpdrPlugin* rdpdr, BOOL userLoggedOn) +{ + size_t pos = 0; + wStream* s = NULL; + size_t count_pos = 0; + struct device_announce_arg arg = { 0 }; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->devman); + + if (userLoggedOn) + { + rdpdr->userLoggedOn = TRUE; + } + + s = StreamPool_Take(rdpdr->pool, 256); + + if (!s) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_DEVICELIST_ANNOUNCE); /* PacketId (2 bytes) */ + count_pos = Stream_GetPosition(s); + Stream_Seek_UINT32(s); /* deviceCount */ + + arg.rdpdr = rdpdr; + arg.userLoggedOn = userLoggedOn; + arg.s = s; + if (!device_foreach(rdpdr, TRUE, device_announce, &arg)) + return ERROR_INVALID_DATA; + + if (arg.count == 0) + { + Stream_Release(s); + return CHANNEL_RC_OK; + } + pos = Stream_GetPosition(s); + Stream_SetPosition(s, count_pos); + Stream_Write_UINT32(s, arg.count); + Stream_SetPosition(s, pos); + Stream_SealLength(s); + return rdpdr_send(rdpdr, s); +} + +UINT rdpdr_try_send_device_list_announce_request(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + if (rdpdr->state != RDPDR_CHANNEL_STATE_USER_LOGGEDON) + { + WLog_Print(rdpdr->log, WLOG_DEBUG, + "hotplug event received, but channel [RDPDR] is not ready (state %s), ignoring.", + rdpdr_state_str(rdpdr->state)); + return CHANNEL_RC_OK; + } + return rdpdr_send_device_list_announce_request(rdpdr, TRUE); +} + +static UINT dummy_irp_response(rdpdrPlugin* rdpdr, wStream* s) +{ + wStream* output = NULL; + UINT32 DeviceId = 0; + UINT32 FileId = 0; + UINT32 CompletionId = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + output = StreamPool_Take(rdpdr->pool, 256); // RDPDR_DEVICE_IO_RESPONSE_LENGTH + if (!output) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(s, 4); /* see "rdpdr_process_receive" */ + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */ + Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */ + + if (!rdpdr_write_iocompletion_header(output, DeviceId, CompletionId, + (UINT32)STATUS_UNSUCCESSFUL)) + return CHANNEL_RC_NO_MEMORY; + + return rdpdr_send(rdpdr, output); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_irp(rdpdrPlugin* rdpdr, wStream* s) +{ + IRP* irp = NULL; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + irp = irp_new(rdpdr->devman, rdpdr->pool, s, rdpdr->log, &error); + + if (!irp) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "irp_new failed with %" PRIu32 "!", error); + + if (error == CHANNEL_RC_OK || (error == ERROR_DEV_NOT_EXIST && rdpdr->ignoreInvalidDevices)) + { + return dummy_irp_response(rdpdr, s); + } + + return error; + } + + if (irp->device->IRPRequest) + IFCALLRET(irp->device->IRPRequest, error, irp->device, irp); + else + irp->Discard(irp); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "device->IRPRequest failed with error %" PRIu32 "", + error); + irp->Discard(irp); + } + + return error; +} + +static UINT rdpdr_process_component(rdpdrPlugin* rdpdr, UINT16 component, UINT16 packetId, + wStream* s) +{ + UINT32 type = 0; + DEVICE* device = NULL; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + switch (component) + { + case RDPDR_CTYP_PRN: + type = RDPDR_DTYP_PRINT; + break; + + default: + return ERROR_INVALID_DATA; + } + + device = devman_get_device_by_type(rdpdr->devman, type); + + if (!device) + return ERROR_DEV_NOT_EXIST; + + return IFCALLRESULT(ERROR_INVALID_PARAMETER, device->CustomComponentRequest, device, component, + packetId, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static BOOL device_init(ULONG_PTR key, void* element, void* data) +{ + wLog* log = data; + UINT error = CHANNEL_RC_OK; + DEVICE* device = element; + + WINPR_UNUSED(key); + WINPR_UNUSED(data); + + IFCALLRET(device->Init, error, device); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(log, WLOG_ERROR, "Device init failed with %s", WTSErrorToString(error)); + return FALSE; + } + return TRUE; +} + +static UINT rdpdr_process_init(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->devman); + + rdpdr->userLoggedOn = FALSE; /* reset possible received state */ + if (!device_foreach(rdpdr, TRUE, device_init, rdpdr->log)) + return ERROR_INTERNAL_ERROR; + return CHANNEL_RC_OK; +} + +static BOOL state_match(enum RDPDR_CHANNEL_STATE state, size_t count, va_list ap) +{ + for (size_t x = 0; x < count; x++) + { + enum RDPDR_CHANNEL_STATE cur = va_arg(ap, enum RDPDR_CHANNEL_STATE); + if (state == cur) + return TRUE; + } + return FALSE; +} + +static const char* state_str(size_t count, va_list ap, char* buffer, size_t size) +{ + for (size_t x = 0; x < count; x++) + { + enum RDPDR_CHANNEL_STATE cur = va_arg(ap, enum RDPDR_CHANNEL_STATE); + const char* curstr = rdpdr_state_str(cur); + winpr_str_append(curstr, buffer, size, "|"); + } + return buffer; +} + +static BOOL rdpdr_state_check(rdpdrPlugin* rdpdr, UINT16 packetid, enum RDPDR_CHANNEL_STATE next, + size_t count, ...) +{ + va_list ap; + WINPR_ASSERT(rdpdr); + + va_start(ap, count); + BOOL rc = state_match(rdpdr->state, count, ap); + va_end(ap); + + if (!rc) + { + const char* strstate = rdpdr_state_str(rdpdr->state); + char buffer[256] = { 0 }; + + va_start(ap, count); + state_str(count, ap, buffer, sizeof(buffer)); + va_end(ap); + + WLog_Print(rdpdr->log, WLOG_ERROR, + "channel [RDPDR] received %s, expected states [%s] but have state %s, aborting.", + rdpdr_packetid_string(packetid), buffer, strstate); + + rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_INITIAL); + return FALSE; + } + return rdpdr_state_advance(rdpdr, next); +} + +static BOOL rdpdr_check_channel_state(rdpdrPlugin* rdpdr, UINT16 packetid) +{ + WINPR_ASSERT(rdpdr); + + switch (packetid) + { + case PAKID_CORE_SERVER_ANNOUNCE: + /* windows servers sometimes send this message. + * it seems related to session login (e.g. first initialization for RDP/TLS style login, + * then reinitialize the channel after login successful + */ + rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_INITIAL); + return rdpdr_state_check(rdpdr, packetid, RDPDR_CHANNEL_STATE_ANNOUNCE, 1, + RDPDR_CHANNEL_STATE_INITIAL); + case PAKID_CORE_SERVER_CAPABILITY: + return rdpdr_state_check(rdpdr, packetid, RDPDR_CHANNEL_STATE_SERVER_CAPS, 6, + RDPDR_CHANNEL_STATE_NAME_REQUEST, + RDPDR_CHANNEL_STATE_SERVER_CAPS, RDPDR_CHANNEL_STATE_READY, + RDPDR_CHANNEL_STATE_CLIENT_CAPS, PAKID_CORE_CLIENTID_CONFIRM, + PAKID_CORE_USER_LOGGEDON); + case PAKID_CORE_CLIENTID_CONFIRM: + return rdpdr_state_check(rdpdr, packetid, RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, 3, + RDPDR_CHANNEL_STATE_CLIENT_CAPS, RDPDR_CHANNEL_STATE_READY, + RDPDR_CHANNEL_STATE_USER_LOGGEDON); + case PAKID_CORE_USER_LOGGEDON: + if (!rdpdr_check_extended_pdu_flag(rdpdr, RDPDR_USER_LOGGEDON_PDU)) + return FALSE; + + return rdpdr_state_check( + rdpdr, packetid, RDPDR_CHANNEL_STATE_USER_LOGGEDON, 4, + RDPDR_CHANNEL_STATE_NAME_REQUEST, RDPDR_CHANNEL_STATE_CLIENT_CAPS, + RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, RDPDR_CHANNEL_STATE_READY); + default: + { + enum RDPDR_CHANNEL_STATE state = RDPDR_CHANNEL_STATE_READY; + return rdpdr_state_check(rdpdr, packetid, state, 1, state); + } + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_receive(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 component = 0; + UINT16 packetId = 0; + UINT32 deviceId = 0; + UINT32 status = 0; + UINT error = ERROR_INVALID_DATA; + + if (!rdpdr || !s) + return CHANNEL_RC_NULL_DATA; + + rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, "[rdpdr-channel] receive"); + if (Stream_GetRemainingLength(s) >= 4) + { + Stream_Read_UINT16(s, component); /* Component (2 bytes) */ + Stream_Read_UINT16(s, packetId); /* PacketId (2 bytes) */ + + if (component == RDPDR_CTYP_CORE) + { + if (!rdpdr_check_channel_state(rdpdr, packetId)) + return CHANNEL_RC_OK; + + switch (packetId) + { + case PAKID_CORE_SERVER_ANNOUNCE: + if ((error = rdpdr_process_server_announce_request(rdpdr, s))) + { + } + else if ((error = rdpdr_send_client_announce_reply(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_client_announce_reply failed with error %" PRIu32 "", + error); + } + else if ((error = rdpdr_send_client_name_request(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_client_name_request failed with error %" PRIu32 "", + error); + } + else if ((error = rdpdr_process_init(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_process_init failed with error %" PRIu32 "", error); + } + + break; + + case PAKID_CORE_SERVER_CAPABILITY: + if ((error = rdpdr_process_capability_request(rdpdr, s))) + { + } + else if ((error = rdpdr_send_capability_response(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_capability_response failed with error %" PRIu32 "", + error); + } + + break; + + case PAKID_CORE_CLIENTID_CONFIRM: + if ((error = rdpdr_process_server_clientid_confirm(rdpdr, s))) + { + } + else if ((error = rdpdr_send_device_list_announce_request(rdpdr, FALSE))) + { + WLog_Print( + rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_announce_request failed with error %" PRIu32 "", + error); + } + else if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_READY)) + { + error = ERROR_INTERNAL_ERROR; + } + break; + + case PAKID_CORE_USER_LOGGEDON: + if ((error = rdpdr_send_device_list_announce_request(rdpdr, TRUE))) + { + WLog_Print( + rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_announce_request failed with error %" PRIu32 "", + error); + } + else if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_READY)) + { + error = ERROR_INTERNAL_ERROR; + } + + break; + + case PAKID_CORE_DEVICE_REPLY: + + /* connect to a specific resource */ + if (Stream_GetRemainingLength(s) >= 8) + { + Stream_Read_UINT32(s, deviceId); + Stream_Read_UINT32(s, status); + + if (status != 0) + devman_unregister_device(rdpdr->devman, (void*)((size_t)deviceId)); + error = CHANNEL_RC_OK; + } + + break; + + case PAKID_CORE_DEVICE_IOREQUEST: + if ((error = rdpdr_process_irp(rdpdr, s))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_process_irp failed with error %" PRIu32 "", error); + return error; + } + else + s = NULL; + + break; + + default: + WLog_Print(rdpdr->log, WLOG_ERROR, + "RDPDR_CTYP_CORE unknown PacketId: 0x%04" PRIX16 "", packetId); + error = ERROR_INVALID_DATA; + break; + } + } + else + { + error = rdpdr_process_component(rdpdr, component, packetId, s); + + if (error != CHANNEL_RC_OK) + { + DWORD level = WLOG_ERROR; + if (rdpdr->ignoreInvalidDevices) + { + if (error == ERROR_DEV_NOT_EXIST) + { + level = WLOG_WARN; + error = CHANNEL_RC_OK; + } + } + WLog_Print(rdpdr->log, level, + "Unknown message: Component: %s [0x%04" PRIX16 + "] PacketId: %s [0x%04" PRIX16 "]", + rdpdr_component_string(component), component, + rdpdr_packetid_string(packetId), packetId); + } + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpdr_send(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT status = 0; + rdpdrPlugin* plugin = (rdpdrPlugin*)rdpdr; + + if (!rdpdr || !s) + { + Stream_Release(s); + return CHANNEL_RC_NULL_DATA; + } + + if (!plugin) + { + Stream_Release(s); + status = CHANNEL_RC_BAD_INIT_HANDLE; + } + else + { + const size_t pos = Stream_GetPosition(s); + rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, "[rdpdr-channel] send"); + status = plugin->channelEntryPoints.pVirtualChannelWriteEx( + plugin->InitHandle, plugin->OpenHandle, Stream_Buffer(s), pos, s); + } + + if (status != CHANNEL_RC_OK) + { + Stream_Release(s); + WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_data_received(rdpdrPlugin* rdpdr, void* pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + wStream* data_in = NULL; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(pData || (dataLength == 0)); + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + /* + * According to MS-RDPBCGR 2.2.6.1, "All virtual channel traffic MUST be suspended. + * This flag is only valid in server-to-client virtual channel traffic. It MUST be + * ignored in client-to-server data." Thus it would be best practice to cease data + * transmission. However, simply returning here avoids a crash. + */ + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (rdpdr->data_in != NULL) + Stream_Release(rdpdr->data_in); + + rdpdr->data_in = StreamPool_Take(rdpdr->pool, totalLength); + + if (!rdpdr->data_in) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + data_in = rdpdr->data_in; + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + const size_t pos = Stream_GetPosition(data_in); + const size_t cap = Stream_Capacity(data_in); + if (cap < pos) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_virtual_channel_event_data_received: read error"); + return ERROR_INTERNAL_ERROR; + } + + Stream_SealLength(data_in); + Stream_SetPosition(data_in, 0); + + if (rdpdr->async) + { + if (!MessageQueue_Post(rdpdr->queue, NULL, 0, (void*)data_in, NULL)) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + rdpdr->data_in = NULL; + } + else + { + UINT error = rdpdr_process_receive(rdpdr, data_in); + Stream_Release(data_in); + rdpdr->data_in = NULL; + if (error) + return error; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE rdpdr_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + rdpdrPlugin* rdpdr = (rdpdrPlugin*)lpUserParam; + + WINPR_ASSERT(rdpdr); + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!rdpdr || !pData || (rdpdr->OpenHandle != openHandle)) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "error no match"); + return; + } + if ((error = rdpdr_virtual_channel_event_data_received(rdpdr, pData, dataLength, + totalLength, dataFlags))) + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_virtual_channel_event_data_received failed with error %" PRIu32 + "!", + error); + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Release(s); + } + break; + + case CHANNEL_EVENT_USER: + break; + } + + if (error && rdpdr && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_open_event_ex reported an error"); + + return; +} + +static DWORD WINAPI rdpdr_virtual_channel_client_thread(LPVOID arg) +{ + rdpdrPlugin* rdpdr = (rdpdrPlugin*)arg; + UINT error = 0; + + if (!rdpdr) + { + ExitThread((DWORD)CHANNEL_RC_NULL_DATA); + return CHANNEL_RC_NULL_DATA; + } + + if ((error = rdpdr_process_connect(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "rdpdr_process_connect failed with error %" PRIu32 "!", + error); + + if (rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; + } + + while (1) + { + wMessage message = { 0 }; + WINPR_ASSERT(rdpdr); + + if (!MessageQueue_Wait(rdpdr->queue)) + break; + + if (MessageQueue_Peek(rdpdr->queue, &message, TRUE)) + { + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + wStream* data = (wStream*)message.wParam; + + error = rdpdr_process_receive(rdpdr, data); + + Stream_Release(data); + if (error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_process_receive failed with error %" PRIu32 "!", error); + + if (rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_client_thread reported an error"); + + goto fail; + } + } + } + } + +fail: + if ((error = drive_hotplug_thread_terminate(rdpdr))) + WLog_Print(rdpdr->log, WLOG_ERROR, + "drive_hotplug_thread_terminate failed with error %" PRIu32 "!", error); + + ExitThread(error); + return error; +} + +static void queue_free(void* obj) +{ + wStream* s = NULL; + wMessage* msg = (wMessage*)obj; + + if (!msg || (msg->id != 0)) + return; + + s = (wStream*)msg->wParam; + WINPR_ASSERT(s); + Stream_Release(s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_connected(rdpdrPlugin* rdpdr, LPVOID pData, + UINT32 dataLength) +{ + wObject* obj = NULL; + + WINPR_ASSERT(rdpdr); + WINPR_UNUSED(pData); + WINPR_UNUSED(dataLength); + + if (rdpdr->async) + { + rdpdr->queue = MessageQueue_New(NULL); + + if (!rdpdr->queue) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "MessageQueue_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + obj = MessageQueue_Object(rdpdr->queue); + obj->fnObjectFree = queue_free; + + if (!(rdpdr->thread = CreateThread(NULL, 0, rdpdr_virtual_channel_client_thread, + (void*)rdpdr, 0, NULL))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "CreateThread failed!"); + return ERROR_INTERNAL_ERROR; + } + } + else + { + UINT error = rdpdr_process_connect(rdpdr); + if (error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_process_connect failed with error %" PRIu32 "!", error); + return error; + } + } + + return rdpdr->channelEntryPoints.pVirtualChannelOpenEx(rdpdr->InitHandle, &rdpdr->OpenHandle, + rdpdr->channelDef.name, + rdpdr_virtual_channel_open_event_ex); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_disconnected(rdpdrPlugin* rdpdr) +{ + UINT error = 0; + + WINPR_ASSERT(rdpdr); + + if (rdpdr->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (rdpdr->queue && rdpdr->thread) + { + if (MessageQueue_PostQuit(rdpdr->queue, 0) && + (WaitForSingleObject(rdpdr->thread, INFINITE) == WAIT_FAILED)) + { + error = GetLastError(); + WLog_Print(rdpdr->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!", + error); + return error; + } + } + + if (rdpdr->thread) + CloseHandle(rdpdr->thread); + MessageQueue_Free(rdpdr->queue); + rdpdr->queue = NULL; + rdpdr->thread = NULL; + + WINPR_ASSERT(rdpdr->channelEntryPoints.pVirtualChannelCloseEx); + error = rdpdr->channelEntryPoints.pVirtualChannelCloseEx(rdpdr->InitHandle, rdpdr->OpenHandle); + + if (CHANNEL_RC_OK != error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(error), error); + } + + rdpdr->OpenHandle = 0; + + if (rdpdr->data_in) + { + Stream_Release(rdpdr->data_in); + rdpdr->data_in = NULL; + } + + if (rdpdr->devman) + { + devman_free(rdpdr->devman); + rdpdr->devman = NULL; + } + + return error; +} + +static void rdpdr_virtual_channel_event_terminated(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + rdpdr->InitHandle = 0; + StreamPool_Free(rdpdr->pool); + free(rdpdr); +} + +static VOID VCAPITYPE rdpdr_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + rdpdrPlugin* rdpdr = (rdpdrPlugin*)lpUserParam; + + if (!rdpdr || (rdpdr->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + WINPR_ASSERT(pData || (dataLength == 0)); + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + break; + + case CHANNEL_EVENT_CONNECTED: + if ((error = rdpdr_virtual_channel_event_connected(rdpdr, pData, dataLength))) + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_virtual_channel_event_connected failed with error %" PRIu32 "!", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = rdpdr_virtual_channel_event_disconnected(rdpdr))) + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_virtual_channel_event_disconnected failed with error %" PRIu32 + "!", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + rdpdr_virtual_channel_event_terminated(rdpdr); + rdpdr = NULL; + break; + + case CHANNEL_EVENT_ATTACHED: + case CHANNEL_EVENT_DETACHED: + default: + WLog_Print(rdpdr->log, WLOG_ERROR, "unknown event %" PRIu32 "!", event); + break; + } + + if (error && rdpdr && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_init_event_ex reported an error"); +} + +/* rdpdr is always built-in */ +#define VirtualChannelEntryEx rdpdr_VirtualChannelEntryEx + +FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints, + PVOID pInitHandle)) +{ + UINT rc = 0; + rdpdrPlugin* rdpdr = NULL; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL; + + WINPR_ASSERT(pEntryPoints); + WINPR_ASSERT(pInitHandle); + + rdpdr = (rdpdrPlugin*)calloc(1, sizeof(rdpdrPlugin)); + + if (!rdpdr) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + rdpdr->log = WLog_Get(TAG); + + rdpdr->clientExtendedPDU = + RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU | RDPDR_USER_LOGGEDON_PDU; + rdpdr->clientIOCode1 = + RDPDR_IRP_MJ_CREATE | RDPDR_IRP_MJ_CLEANUP | RDPDR_IRP_MJ_CLOSE | RDPDR_IRP_MJ_READ | + RDPDR_IRP_MJ_WRITE | RDPDR_IRP_MJ_FLUSH_BUFFERS | RDPDR_IRP_MJ_SHUTDOWN | + RDPDR_IRP_MJ_DEVICE_CONTROL | RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION | + RDPDR_IRP_MJ_SET_VOLUME_INFORMATION | RDPDR_IRP_MJ_QUERY_INFORMATION | + RDPDR_IRP_MJ_SET_INFORMATION | RDPDR_IRP_MJ_DIRECTORY_CONTROL | RDPDR_IRP_MJ_LOCK_CONTROL | + RDPDR_IRP_MJ_QUERY_SECURITY | RDPDR_IRP_MJ_SET_SECURITY; + + rdpdr->clientExtraFlags1 = ENABLE_ASYNCIO; + + rdpdr->pool = StreamPool_New(TRUE, 1024); + if (!rdpdr->pool) + { + free(rdpdr); + return FALSE; + } + + rdpdr->channelDef.options = + CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP; + sprintf_s(rdpdr->channelDef.name, ARRAYSIZE(rdpdr->channelDef.name), RDPDR_SVC_CHANNEL_NAME); + rdpdr->sequenceId = 0; + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + rdpdr->rdpcontext = pEntryPointsEx->context; + if (!freerdp_settings_get_bool(rdpdr->rdpcontext->settings, + FreeRDP_SynchronousStaticChannels)) + rdpdr->async = TRUE; + } + + CopyMemory(&(rdpdr->channelEntryPoints), pEntryPoints, sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + rdpdr->InitHandle = pInitHandle; + rc = rdpdr->channelEntryPoints.pVirtualChannelInitEx( + rdpdr, NULL, pInitHandle, &rdpdr->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + rdpdr_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelInitEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(rc), rc); + free(rdpdr); + return FALSE; + } + + return TRUE; +} diff --git a/channels/rdpdr/client/rdpdr_main.h b/channels/rdpdr/client/rdpdr_main.h new file mode 100644 index 0000000..91131cb --- /dev/null +++ b/channels/rdpdr/client/rdpdr_main.h @@ -0,0 +1,121 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.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_CHANNEL_RDPDR_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/stream.h> +#include <winpr/collections.h> + +#include <freerdp/api.h> +#include <freerdp/svc.h> +#include <freerdp/addin.h> + +#include <freerdp/channels/rdpdr.h> +#include <freerdp/channels/log.h> + +#ifdef __MACOSX__ +#include <CoreServices/CoreServices.h> +#endif + +enum RDPDR_CHANNEL_STATE +{ + RDPDR_CHANNEL_STATE_INITIAL = 0, + RDPDR_CHANNEL_STATE_ANNOUNCE, + RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY, + RDPDR_CHANNEL_STATE_NAME_REQUEST, + RDPDR_CHANNEL_STATE_SERVER_CAPS, + RDPDR_CHANNEL_STATE_CLIENT_CAPS, + RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, + RDPDR_CHANNEL_STATE_READY, + RDPDR_CHANNEL_STATE_USER_LOGGEDON +}; + +typedef struct +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + enum RDPDR_CHANNEL_STATE state; + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + + DEVMAN* devman; + BOOL ignoreInvalidDevices; + + UINT32 serverOsType; + UINT32 serverOsVersion; + UINT16 serverVersionMajor; + UINT16 serverVersionMinor; + UINT32 serverExtendedPDU; + UINT32 serverIOCode1; + UINT32 serverIOCode2; + UINT32 serverExtraFlags1; + UINT32 serverExtraFlags2; + UINT32 serverSpecialTypeDeviceCap; + + UINT32 clientOsType; + UINT32 clientOsVersion; + UINT16 clientVersionMajor; + UINT16 clientVersionMinor; + UINT32 clientExtendedPDU; + UINT32 clientIOCode1; + UINT32 clientIOCode2; + UINT32 clientExtraFlags1; + UINT32 clientExtraFlags2; + UINT32 clientSpecialTypeDeviceCap; + + UINT32 clientID; + char computerName[256]; + + UINT32 sequenceId; + BOOL userLoggedOn; + + /* hotplug support */ + HANDLE hotplugThread; +#ifdef _WIN32 + HWND hotplug_wnd; +#endif +#ifdef __MACOSX__ + CFRunLoopRef runLoop; +#endif +#ifndef _WIN32 + HANDLE stopEvent; +#endif + rdpContext* rdpcontext; + wStreamPool* pool; + wLog* log; + BOOL async; +} rdpdrPlugin; + +BOOL rdpdr_state_advance(rdpdrPlugin* rdpdr, enum RDPDR_CHANNEL_STATE next); +UINT rdpdr_send(rdpdrPlugin* rdpdr, wStream* s); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H */ diff --git a/channels/rdpdr/server/CMakeLists.txt b/channels/rdpdr/server/CMakeLists.txt new file mode 100644 index 0000000..7d1f7e5 --- /dev/null +++ b/channels/rdpdr/server/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP 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. + +define_channel_server("rdpdr") + +set(${MODULE_PREFIX}_SRCS + rdpdr_main.c + rdpdr_main.h +) + +set(${MODULE_PREFIX}_LIBS + freerdp +) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/channels/rdpdr/server/rdpdr_main.c b/channels/rdpdr/server/rdpdr_main.c new file mode 100644 index 0000000..bad6e23 --- /dev/null +++ b/channels/rdpdr/server/rdpdr_main.c @@ -0,0 +1,3574 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel Extension + * + * Copyright 2014 Dell Software <Mike.McDonald@software.dell.com> + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015-2022 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2022 Armin Novak <anovak@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/freerdp.h> +#include <freerdp/utils/rdpdr_utils.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/nt.h> +#include <winpr/print.h> +#include <winpr/stream.h> + +#include <freerdp/channels/log.h> +#include "rdpdr_main.h" + +#define RDPDR_ADD_PRINTER_EVENT 0x00000001 +#define RDPDR_UPDATE_PRINTER_EVENT 0x00000002 +#define RDPDR_DELETE_PRINTER_EVENT 0x00000003 +#define RDPDR_RENAME_PRINTER_EVENT 0x00000004 + +#define RDPDR_HEADER_LENGTH 4 +#define RDPDR_CAPABILITY_HEADER_LENGTH 8 + +struct s_rdpdr_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; + + UINT32 ClientId; + UINT16 VersionMajor; + UINT16 VersionMinor; + char* ClientComputerName; + + BOOL UserLoggedOnPdu; + + wListDictionary* IrpList; + UINT32 NextCompletionId; + + wHashTable* devicelist; + wLog* log; +}; + +static void rdpdr_device_free(RdpdrDevice* device) +{ + if (!device) + return; + free(device->DeviceData); + free(device); +} + +static void rdpdr_device_free_h(void* obj) +{ + RdpdrDevice* other = obj; + rdpdr_device_free(other); +} + +static UINT32 rdpdr_deviceid_hash(const void* id) +{ + WINPR_ASSERT(id); + return *((const UINT32*)id); +} + +static RdpdrDevice* rdpdr_device_new(void) +{ + return calloc(1, sizeof(RdpdrDevice)); +} + +static void* rdpdr_device_clone(const void* val) +{ + const RdpdrDevice* other = val; + + if (!other) + return NULL; + + RdpdrDevice* tmp = rdpdr_device_new(); + if (!tmp) + goto fail; + + *tmp = *other; + if (other->DeviceData) + { + tmp->DeviceData = malloc(other->DeviceDataLength); + if (!tmp->DeviceData) + goto fail; + memcpy(tmp->DeviceData, other->DeviceData, other->DeviceDataLength); + } + return tmp; + +fail: + rdpdr_device_free(tmp); + return NULL; +} + +static RdpdrDevice* rdpdr_get_device_by_id(RdpdrServerPrivate* priv, UINT32 DeviceId) +{ + WINPR_ASSERT(priv); + + return HashTable_GetItemValue(priv->devicelist, &DeviceId); +} + +static BOOL rdpdr_remove_device_by_id(RdpdrServerPrivate* priv, UINT32 DeviceId) +{ + const RdpdrDevice* device = rdpdr_get_device_by_id(priv, DeviceId); + WINPR_ASSERT(priv); + + if (!device) + { + WLog_Print(priv->log, WLOG_WARN, "[del] Device Id: 0x%08" PRIX32 ": no such device", + DeviceId); + return FALSE; + } + WLog_Print(priv->log, WLOG_DEBUG, + "[del] Device Name: %s Id: 0x%08" PRIX32 " DataLength: %" PRIu32 "", + device->PreferredDosName, device->DeviceId, device->DeviceDataLength); + return HashTable_Remove(priv->devicelist, &DeviceId); +} + +static BOOL rdpdr_add_device(RdpdrServerPrivate* priv, const RdpdrDevice* device) +{ + WINPR_ASSERT(priv); + WINPR_ASSERT(device); + + WLog_Print(priv->log, WLOG_DEBUG, + "[add] Device Name: %s Id: 0x%08" PRIX32 " DataLength: %" PRIu32 "", + device->PreferredDosName, device->DeviceId, device->DeviceDataLength); + + return HashTable_Insert(priv->devicelist, &device->DeviceId, device); +} + +static UINT32 g_ClientId = 0; + +static const WCHAR* rdpdr_read_ustring(wLog* log, wStream* s, size_t bytelen) +{ + const size_t charlen = (bytelen + 1) / sizeof(WCHAR); + const WCHAR* str = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, bytelen)) + return NULL; + if (_wcsnlen(str, charlen) == charlen) + { + WLog_Print(log, WLOG_WARN, "[rdpdr] unicode string not '\0' terminated"); + return NULL; + } + Stream_Seek(s, bytelen); + return str; +} + +static RDPDR_IRP* rdpdr_server_irp_new(void) +{ + RDPDR_IRP* irp = (RDPDR_IRP*)calloc(1, sizeof(RDPDR_IRP)); + return irp; +} + +static void rdpdr_server_irp_free(RDPDR_IRP* irp) +{ + free(irp); +} + +static BOOL rdpdr_server_enqueue_irp(RdpdrServerContext* context, RDPDR_IRP* irp) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + const uintptr_t key = irp->CompletionId + 1ull; + return ListDictionary_Add(context->priv->IrpList, (void*)key, irp); +} + +static RDPDR_IRP* rdpdr_server_dequeue_irp(RdpdrServerContext* context, UINT32 completionId) +{ + RDPDR_IRP* irp = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + const uintptr_t key = completionId + 1ull; + irp = (RDPDR_IRP*)ListDictionary_Take(context->priv->IrpList, (void*)key); + return irp; +} + +static UINT rdpdr_seal_send_free_request(RdpdrServerContext* context, wStream* s) +{ + BOOL status = 0; + size_t length = 0; + ULONG written = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + + Stream_SealLength(s); + length = Stream_Length(s); + WINPR_ASSERT(length <= ULONG_MAX); + Stream_SetPosition(s, 0); + + if (length >= RDPDR_HEADER_LENGTH) + { + RDPDR_HEADER header = { 0 }; + Stream_Read_UINT16(s, header.Component); + Stream_Read_UINT16(s, header.PacketId); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "sending message {Component %s[%04" PRIx16 "], PacketId %s[%04" PRIx16 "]", + rdpdr_component_string(header.Component), header.Component, + rdpdr_packetid_string(header.PacketId), header.PacketId); + } + winpr_HexLogDump(context->priv->log, WLOG_DEBUG, Stream_Buffer(s), Stream_Length(s)); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, (PCHAR)Stream_Buffer(s), + (ULONG)length, &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_announce_request(RdpdrServerContext* context) +{ + UINT error = 0; + wStream* s = NULL; + RDPDR_HEADER header = { 0 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_SERVER_ANNOUNCE; + + error = IFCALLRESULT(CHANNEL_RC_OK, context->SendServerAnnounce, context); + if (error != CHANNEL_RC_OK) + return error; + + s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 8); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->priv->ClientId); /* ClientId (4 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_announce_response(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 ClientId = 0; + UINT16 VersionMajor = 0; + UINT16 VersionMinor = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Read_UINT16(s, VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Read_UINT32(s, ClientId); /* ClientId (4 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, + "Client Announce Response: VersionMajor: 0x%08" PRIX16 " VersionMinor: 0x%04" PRIX16 + " ClientId: 0x%08" PRIX32 "", + VersionMajor, VersionMinor, ClientId); + context->priv->ClientId = ClientId; + + return IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveAnnounceResponse, context, VersionMajor, + VersionMinor, ClientId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_client_name_request(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 UnicodeFlag = 0; + UINT32 CodePage = 0; + UINT32 ComputerNameLen = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, UnicodeFlag); /* UnicodeFlag (4 bytes) */ + Stream_Read_UINT32(s, CodePage); /* CodePage (4 bytes), MUST be set to zero */ + Stream_Read_UINT32(s, ComputerNameLen); /* ComputerNameLen (4 bytes) */ + /* UnicodeFlag is either 0 or 1, the other 31 bits must be ignored. + */ + UnicodeFlag = UnicodeFlag & 0x00000001; + + if (CodePage != 0) + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.2.4 Client Name Request (DR_CORE_CLIENT_NAME_REQ)::CodePage " + "must be 0, but is 0x%08" PRIx32, + CodePage); + + /** + * Caution: ComputerNameLen is given *bytes*, + * not in characters, including the NULL terminator! + */ + + if (UnicodeFlag) + { + if ((ComputerNameLen % 2) || ComputerNameLen > 512 || ComputerNameLen < 2) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "invalid unicode computer name length: %" PRIu32 "", ComputerNameLen); + return ERROR_INVALID_DATA; + } + } + else + { + if (ComputerNameLen > 256 || ComputerNameLen < 1) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "invalid ascii computer name length: %" PRIu32 "", ComputerNameLen); + return ERROR_INVALID_DATA; + } + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, ComputerNameLen)) + return ERROR_INVALID_DATA; + + /* ComputerName must be null terminated, check if it really is */ + const char* computerName = Stream_ConstPointer(s); + if (computerName[ComputerNameLen - 1] || (UnicodeFlag && computerName[ComputerNameLen - 2])) + { + WLog_Print(context->priv->log, WLOG_ERROR, "computer name must be null terminated"); + return ERROR_INVALID_DATA; + } + + if (context->priv->ClientComputerName) + { + free(context->priv->ClientComputerName); + context->priv->ClientComputerName = NULL; + } + + if (UnicodeFlag) + { + context->priv->ClientComputerName = + Stream_Read_UTF16_String_As_UTF8(s, ComputerNameLen / sizeof(WCHAR), NULL); + if (!context->priv->ClientComputerName) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed to convert client computer name"); + return ERROR_INVALID_DATA; + } + } + else + { + const char* name = Stream_ConstPointer(s); + context->priv->ClientComputerName = _strdup(name); + Stream_Seek(s, ComputerNameLen); + + if (!context->priv->ClientComputerName) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed to duplicate client computer name"); + return CHANNEL_RC_NO_MEMORY; + } + } + + WLog_Print(context->priv->log, WLOG_DEBUG, "ClientComputerName: %s", + context->priv->ClientComputerName); + return IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveClientNameRequest, context, ComputerNameLen, + context->priv->ClientComputerName); +} + +static UINT rdpdr_server_write_capability_set_header_cb(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + UINT error = rdpdr_write_capset_header(context->priv->log, s, header); + if (error != CHANNEL_RC_OK) + return error; + + return IFCALLRESULT(CHANNEL_RC_OK, context->SendCaps, context, header, 0, NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_general_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + UINT32 ioCode1 = 0; + UINT32 extraFlags1 = 0; + UINT32 extendedPdu = 0; + UINT16 VersionMajor = 0; + UINT16 VersionMinor = 0; + UINT32 SpecialTypeDeviceCap = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Seek_UINT32(s); /* osType (4 bytes), ignored on receipt */ + Stream_Seek_UINT32(s); /* osVersion (4 bytes), unused and must be set to zero */ + Stream_Read_UINT16(s, VersionMajor); /* protocolMajorVersion (2 bytes) */ + Stream_Read_UINT16(s, VersionMinor); /* protocolMinorVersion (2 bytes) */ + Stream_Read_UINT32(s, ioCode1); /* ioCode1 (4 bytes) */ + Stream_Seek_UINT32(s); /* ioCode2 (4 bytes), must be set to zero, reserved for future use */ + Stream_Read_UINT32(s, extendedPdu); /* extendedPdu (4 bytes) */ + Stream_Read_UINT32(s, extraFlags1); /* extraFlags1 (4 bytes) */ + Stream_Seek_UINT32(s); /* extraFlags2 (4 bytes), must be set to zero, reserved for future use */ + + if (VersionMajor != RDPDR_MAJOR_RDP_VERSION) + { + WLog_Print(context->priv->log, WLOG_ERROR, "unsupported RDPDR version %" PRIu16 ".%" PRIu16, + VersionMajor, VersionMinor); + return ERROR_INVALID_DATA; + } + + switch (VersionMinor) + { + case RDPDR_MINOR_RDP_VERSION_13: + break; + case RDPDR_MINOR_RDP_VERSION_6_X: + break; + case RDPDR_MINOR_RDP_VERSION_5_2: + break; + case RDPDR_MINOR_RDP_VERSION_5_1: + break; + case RDPDR_MINOR_RDP_VERSION_5_0: + break; + default: + WLog_Print(context->priv->log, WLOG_WARN, + "unsupported RDPDR minor version %" PRIu16 ".%" PRIu16, VersionMajor, + VersionMinor); + break; + } + + if (header->Version == GENERAL_CAPABILITY_VERSION_02) + { + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, SpecialTypeDeviceCap); /* SpecialTypeDeviceCap (4 bytes) */ + } + + context->priv->UserLoggedOnPdu = (extendedPdu & RDPDR_USER_LOGGEDON_PDU) ? TRUE : FALSE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_general_capability_set(RdpdrServerContext* context, wStream* s) +{ + UINT32 ioCode1 = 0; + UINT32 extendedPdu = 0; + UINT32 extraFlags1 = 0; + UINT32 SpecialTypeDeviceCap = 0; + const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH + 36, + GENERAL_CAPABILITY_VERSION_02 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + ioCode1 = 0; + ioCode1 |= RDPDR_IRP_MJ_CREATE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_CLEANUP; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_CLOSE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_READ; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_WRITE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_FLUSH_BUFFERS; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SHUTDOWN; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_DEVICE_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SET_VOLUME_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SET_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_DIRECTORY_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_LOCK_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_SECURITY; /* optional */ + ioCode1 |= RDPDR_IRP_MJ_SET_SECURITY; /* optional */ + extendedPdu = 0; + extendedPdu |= RDPDR_CLIENT_DISPLAY_NAME_PDU; /* always set */ + extendedPdu |= RDPDR_DEVICE_REMOVE_PDUS; /* optional */ + + if (context->priv->UserLoggedOnPdu) + extendedPdu |= RDPDR_USER_LOGGEDON_PDU; /* optional */ + + extraFlags1 = 0; + extraFlags1 |= ENABLE_ASYNCIO; /* optional */ + SpecialTypeDeviceCap = 0; + + UINT error = rdpdr_write_capset_header(context->priv->log, s, &header); + if (error != CHANNEL_RC_OK) + return error; + + const BYTE* data = Stream_ConstPointer(s); + const size_t start = Stream_GetPosition(s); + Stream_Write_UINT32(s, 0); /* osType (4 bytes), ignored on receipt */ + Stream_Write_UINT32(s, 0); /* osVersion (4 bytes), unused and must be set to zero */ + Stream_Write_UINT16(s, context->priv->VersionMajor); /* protocolMajorVersion (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMinor); /* protocolMinorVersion (2 bytes) */ + Stream_Write_UINT32(s, ioCode1); /* ioCode1 (4 bytes) */ + Stream_Write_UINT32(s, 0); /* ioCode2 (4 bytes), must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, extendedPdu); /* extendedPdu (4 bytes) */ + Stream_Write_UINT32(s, extraFlags1); /* extraFlags1 (4 bytes) */ + Stream_Write_UINT32( + s, 0); /* extraFlags2 (4 bytes), must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, SpecialTypeDeviceCap); /* SpecialTypeDeviceCap (4 bytes) */ + const size_t end = Stream_GetPosition(s); + return IFCALLRESULT(CHANNEL_RC_OK, context->SendCaps, context, &header, end - start, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_printer_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + WINPR_UNUSED(context); + WINPR_UNUSED(header); + WINPR_UNUSED(s); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_printer_capability_set(RdpdrServerContext* context, wStream* s) +{ + const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + PRINT_CAPABILITY_VERSION_01 }; + WINPR_UNUSED(context); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + return rdpdr_server_write_capability_set_header_cb(context, s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_port_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(context); + WINPR_UNUSED(s); + WINPR_UNUSED(header); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_port_capability_set(RdpdrServerContext* context, wStream* s) +{ + const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + PORT_CAPABILITY_VERSION_01 }; + WINPR_UNUSED(context); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + return rdpdr_server_write_capability_set_header_cb(context, s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_drive_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_UNUSED(context); + WINPR_UNUSED(header); + WINPR_UNUSED(s); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_drive_capability_set(RdpdrServerContext* context, wStream* s) +{ + const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + DRIVE_CAPABILITY_VERSION_02 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_UNUSED(context); + + return rdpdr_server_write_capability_set_header_cb(context, s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_smartcard_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_UNUSED(context); + WINPR_UNUSED(header); + WINPR_UNUSED(s); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_smartcard_capability_set(RdpdrServerContext* context, wStream* s) +{ + const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + SMARTCARD_CAPABILITY_VERSION_01 }; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WINPR_UNUSED(context); + + return rdpdr_server_write_capability_set_header_cb(context, s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_core_capability_request(RdpdrServerContext* context) +{ + wStream* s = NULL; + RDPDR_HEADER header = { 0 }; + UINT16 numCapabilities = 0; + UINT error = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_SERVER_CAPABILITY; + numCapabilities = 1; + + if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0) + numCapabilities++; + + if (((context->supported & RDPDR_DTYP_PARALLEL) != 0) || + ((context->supported & RDPDR_DTYP_SERIAL) != 0)) + numCapabilities++; + + if ((context->supported & RDPDR_DTYP_PRINT) != 0) + numCapabilities++; + + if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0) + numCapabilities++; + + s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 512); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, numCapabilities); /* numCapabilities (2 bytes) */ + Stream_Write_UINT16(s, 0); /* Padding (2 bytes) */ + + if ((error = rdpdr_server_write_general_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_general_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + + if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0) + { + if ((error = rdpdr_server_write_drive_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_drive_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + } + + if (((context->supported & RDPDR_DTYP_PARALLEL) != 0) || + ((context->supported & RDPDR_DTYP_SERIAL) != 0)) + { + if ((error = rdpdr_server_write_port_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_port_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + } + + if ((context->supported & RDPDR_DTYP_PRINT) != 0) + { + if ((error = rdpdr_server_write_printer_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_printer_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + } + + if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0) + { + if ((error = rdpdr_server_write_smartcard_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_printer_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + } + + return rdpdr_seal_send_free_request(context, s); +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_core_capability_response(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT status = 0; + UINT16 numCapabilities = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); /* numCapabilities (2 bytes) */ + Stream_Seek_UINT16(s); /* Padding (2 bytes) */ + + UINT16 caps = 0; + for (UINT16 i = 0; i < numCapabilities; i++) + { + RDPDR_CAPABILITY_HEADER capabilityHeader = { 0 }; + const size_t start = Stream_GetPosition(s); + + if ((status = rdpdr_read_capset_header(context->priv->log, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", status); + return status; + } + + status = IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveCaps, context, &capabilityHeader, + Stream_GetRemainingLength(s), Stream_ConstPointer(s)); + if (status != CHANNEL_RC_OK) + return status; + + caps |= capabilityHeader.CapabilityType; + switch (capabilityHeader.CapabilityType) + { + case CAP_GENERAL_TYPE: + if ((status = + rdpdr_server_read_general_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + case CAP_PRINTER_TYPE: + if ((status = + rdpdr_server_read_printer_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + case CAP_PORT_TYPE: + if ((status = rdpdr_server_read_port_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + case CAP_DRIVE_TYPE: + if ((status = + rdpdr_server_read_drive_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + case CAP_SMARTCARD_TYPE: + if ((status = + rdpdr_server_read_smartcard_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + default: + WLog_Print(context->priv->log, WLOG_WARN, "Unknown capabilityType %" PRIu16 "", + capabilityHeader.CapabilityType); + Stream_Seek(s, capabilityHeader.CapabilityLength); + return ERROR_INVALID_DATA; + } + + for (UINT16 x = 0; x < 16; x++) + { + const UINT16 mask = (UINT16)(1 << x); + if (((caps & mask) != 0) && ((context->supported & mask) == 0)) + { + WLog_Print(context->priv->log, WLOG_WARN, + "client sent capability %s we did not announce!", + freerdp_rdpdr_dtyp_string(mask)); + } + + /* we assume the server supports the capability. so only deactivate what the client did + * not respond with */ + if ((caps & mask) == 0) + context->supported &= ~mask; + } + + const size_t end = Stream_GetPosition(s); + const size_t diff = end - start; + if (diff != capabilityHeader.CapabilityLength + RDPDR_CAPABILITY_HEADER_LENGTH) + { + WLog_Print(context->priv->log, WLOG_WARN, + "{capability %s[0x%04" PRIx16 "]} processed %" PRIuz + " bytes, but expected to be %" PRIu16, + rdpdr_cap_type_string(capabilityHeader.CapabilityType), + capabilityHeader.CapabilityType, diff, capabilityHeader.CapabilityLength); + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_client_id_confirm(RdpdrServerContext* context) +{ + wStream* s = NULL; + RDPDR_HEADER header = { 0 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_CLIENTID_CONFIRM; + s = Stream_New(NULL, RDPDR_HEADER_LENGTH + 8); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->priv->ClientId); /* ClientId (4 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_list_announce_request(RdpdrServerContext* context, + wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 DeviceCount = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DeviceCount); /* DeviceCount (4 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "DeviceCount: %" PRIu32 "", DeviceCount); + + for (UINT32 i = 0; i < DeviceCount; i++) + { + UINT error = 0; + RdpdrDevice device = { 0 }; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, device.DeviceType); /* DeviceType (4 bytes) */ + Stream_Read_UINT32(s, device.DeviceId); /* DeviceId (4 bytes) */ + Stream_Read(s, device.PreferredDosName, 8); /* PreferredDosName (8 bytes) */ + Stream_Read_UINT32(s, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */ + device.DeviceData = Stream_Pointer(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, device.DeviceDataLength)) + return ERROR_INVALID_DATA; + + if (!rdpdr_add_device(context->priv, &device)) + return ERROR_INTERNAL_ERROR; + + error = IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveDeviceAnnounce, context, &device); + if (error != CHANNEL_RC_OK) + return error; + + switch (device.DeviceType) + { + case RDPDR_DTYP_FILESYSTEM: + if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnDriveCreate, context, &device); + break; + + case RDPDR_DTYP_PRINT: + if ((context->supported & RDPDR_DTYP_PRINT) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnPrinterCreate, context, &device); + break; + + case RDPDR_DTYP_SERIAL: + if (device.DeviceDataLength != 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[rdpdr] RDPDR_DTYP_SERIAL::DeviceDataLength != 0 [%" PRIu32 "]", + device.DeviceDataLength); + error = ERROR_INVALID_DATA; + } + else if ((context->supported & RDPDR_DTYP_SERIAL) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnSerialPortCreate, context, &device); + break; + + case RDPDR_DTYP_PARALLEL: + if (device.DeviceDataLength != 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[rdpdr] RDPDR_DTYP_PARALLEL::DeviceDataLength != 0 [%" PRIu32 "]", + device.DeviceDataLength); + error = ERROR_INVALID_DATA; + } + else if ((context->supported & RDPDR_DTYP_PARALLEL) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnParallelPortCreate, context, + &device); + break; + + case RDPDR_DTYP_SMARTCARD: + if (device.DeviceDataLength != 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[rdpdr] RDPDR_DTYP_SMARTCARD::DeviceDataLength != 0 [%" PRIu32 "]", + device.DeviceDataLength); + error = ERROR_INVALID_DATA; + } + else if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnSmartcardCreate, context, &device); + break; + + default: + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.2.9 Client Device List Announce Request " + "(DR_CORE_DEVICELIST_ANNOUNCE_REQ) unknown device type %04" PRIx16 + " at position %" PRIu32, + device.DeviceType, i); + error = ERROR_INVALID_DATA; + break; + } + + if (error != CHANNEL_RC_OK) + return error; + + Stream_Seek(s, device.DeviceDataLength); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_list_remove_request(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 DeviceCount = 0; + UINT32 DeviceType = 0; + UINT32 DeviceId = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DeviceCount); /* DeviceCount (4 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "DeviceCount: %" PRIu32 "", DeviceCount); + + for (UINT32 i = 0; i < DeviceCount; i++) + { + UINT error = 0; + const RdpdrDevice* device = NULL; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + device = rdpdr_get_device_by_id(context->priv, DeviceId); + WLog_Print(context->priv->log, WLOG_DEBUG, "Device %" PRIu32 " Id: 0x%08" PRIX32 "", i, + DeviceId); + DeviceType = 0; + if (device) + DeviceType = device->DeviceType; + + error = + IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveDeviceRemove, context, DeviceId, device); + if (error != CHANNEL_RC_OK) + return error; + + switch (DeviceType) + { + case RDPDR_DTYP_FILESYSTEM: + if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnDriveDelete, context, DeviceId); + break; + + case RDPDR_DTYP_PRINT: + if ((context->supported & RDPDR_DTYP_PRINT) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnPrinterDelete, context, DeviceId); + break; + + case RDPDR_DTYP_SERIAL: + if ((context->supported & RDPDR_DTYP_SERIAL) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnSerialPortDelete, context, DeviceId); + break; + + case RDPDR_DTYP_PARALLEL: + if ((context->supported & RDPDR_DTYP_PARALLEL) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnParallelPortDelete, context, + DeviceId); + break; + + case RDPDR_DTYP_SMARTCARD: + if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnSmartcardDelete, context, DeviceId); + break; + + default: + break; + } + + if (error != CHANNEL_RC_OK) + return error; + + if (!rdpdr_remove_device_by_id(context->priv, DeviceId)) + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_create_request(RdpdrServerContext* context, wStream* s, + UINT32 DeviceId, UINT32 FileId, + UINT32 CompletionId) +{ + const WCHAR* path = NULL; + UINT32 DesiredAccess = 0; + UINT32 AllocationSize = 0; + UINT32 FileAttributes = 0; + UINT32 SharedAccess = 0; + UINT32 CreateDisposition = 0; + UINT32 CreateOptions = 0; + UINT32 PathLength = 0; + + WINPR_ASSERT(context); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DesiredAccess); + Stream_Read_UINT32(s, AllocationSize); + Stream_Read_UINT32(s, FileAttributes); + Stream_Read_UINT32(s, SharedAccess); + Stream_Read_UINT32(s, CreateDisposition); + Stream_Read_UINT32(s, CreateOptions); + Stream_Read_UINT32(s, PathLength); + + path = rdpdr_read_ustring(context->priv->log, s, PathLength); + if (!path && (PathLength > 0)) + return ERROR_INVALID_DATA; + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.1 Device Create Request (DR_CREATE_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_close_request(RdpdrServerContext* context, wStream* s, + UINT32 DeviceId, UINT32 FileId, + UINT32 CompletionId) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Seek(s, 32); /* Padding */ + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.2 Device Close Request (DR_CLOSE_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_read_request(RdpdrServerContext* context, wStream* s, + UINT32 DeviceId, UINT32 FileId, + UINT32 CompletionId) +{ + UINT32 Length = 0; + UINT64 Offset = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Length); + Stream_Read_UINT64(s, Offset); + Stream_Seek(s, 20); /* Padding */ + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.3 Device Read Request (DR_READ_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_write_request(RdpdrServerContext* context, wStream* s, + UINT32 DeviceId, UINT32 FileId, + UINT32 CompletionId) +{ + UINT32 Length = 0; + UINT64 Offset = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Length); + Stream_Read_UINT64(s, Offset); + Stream_Seek(s, 20); /* Padding */ + + const BYTE* data = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.4 Device Write Request (DR_WRITE_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", data); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_device_control_request(RdpdrServerContext* context, wStream* s, + UINT32 DeviceId, UINT32 FileId, + UINT32 CompletionId) +{ + UINT32 OutputBufferLength = 0; + UINT32 InputBufferLength = 0; + UINT32 IoControlCode = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OutputBufferLength); + Stream_Read_UINT32(s, InputBufferLength); + Stream_Read_UINT32(s, IoControlCode); + Stream_Seek(s, 20); /* Padding */ + + const BYTE* InputBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, InputBufferLength)) + return ERROR_INVALID_DATA; + Stream_Seek(s, InputBufferLength); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.5 Device Control Request (DR_CONTROL_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", InputBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_query_volume_information_request(RdpdrServerContext* context, + wStream* s, UINT32 DeviceId, + UINT32 FileId, + UINT32 CompletionId) +{ + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(context); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT32(s, Length); + Stream_Seek(s, 24); /* Padding */ + + const BYTE* QueryVolumeBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.6 Server Drive Query Volume Information Request " + "(DR_DRIVE_QUERY_VOLUME_INFORMATION_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", QueryVolumeBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_set_volume_information_request(RdpdrServerContext* context, + wStream* s, UINT32 DeviceId, + UINT32 FileId, + UINT32 CompletionId) +{ + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT32(s, Length); + Stream_Seek(s, 24); /* Padding */ + + const BYTE* SetVolumeBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.7 Server Drive Set Volume Information Request " + "(DR_DRIVE_SET_VOLUME_INFORMATION_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", SetVolumeBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_query_information_request(RdpdrServerContext* context, + wStream* s, UINT32 DeviceId, + UINT32 FileId, UINT32 CompletionId) +{ + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT32(s, Length); + Stream_Seek(s, 24); /* Padding */ + + const BYTE* QueryBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.8 Server Drive Query Information Request " + "(DR_DRIVE_QUERY_INFORMATION_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", QueryBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_set_information_request(RdpdrServerContext* context, wStream* s, + UINT32 DeviceId, UINT32 FileId, + UINT32 CompletionId) +{ + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT32(s, Length); + Stream_Seek(s, 24); /* Padding */ + + const BYTE* SetBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.9 Server Drive Set Information Request " + "(DR_DRIVE_SET_INFORMATION_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", SetBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_query_directory_request(RdpdrServerContext* context, wStream* s, + UINT32 DeviceId, UINT32 FileId, + UINT32 CompletionId) +{ + BYTE InitialQuery = 0; + UINT32 FsInformationClass = 0; + UINT32 PathLength = 0; + const WCHAR* Path = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT8(s, InitialQuery); + Stream_Read_UINT32(s, PathLength); + Stream_Seek(s, 23); /* Padding */ + + Path = rdpdr_read_ustring(context->priv->log, s, PathLength); + if (!Path && (PathLength > 0)) + return ERROR_INVALID_DATA; + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.10 Server Drive Query Directory Request " + "(DR_DRIVE_QUERY_DIRECTORY_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_change_directory_request(RdpdrServerContext* context, + wStream* s, UINT32 DeviceId, + UINT32 FileId, UINT32 CompletionId) +{ + BYTE WatchTree = 0; + UINT32 CompletionFilter = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, WatchTree); + Stream_Read_UINT32(s, CompletionFilter); + Stream_Seek(s, 27); /* Padding */ + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.11 Server Drive NotifyChange Directory Request " + "(DR_DRIVE_NOTIFY_CHANGE_DIRECTORY_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_directory_control_request(RdpdrServerContext* context, + wStream* s, UINT32 DeviceId, + UINT32 FileId, UINT32 CompletionId, + UINT32 MinorFunction) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + switch (MinorFunction) + { + case IRP_MN_QUERY_DIRECTORY: + return rdpdr_server_receive_io_query_directory_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MN_NOTIFY_CHANGE_DIRECTORY: + return rdpdr_server_receive_io_change_directory_request(context, s, DeviceId, FileId, + CompletionId); + default: + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) " + "MajorFunction=%s, MinorFunction=%08" PRIx32 " is not supported", + rdpdr_irp_string(IRP_MJ_DIRECTORY_CONTROL), MinorFunction); + return ERROR_INVALID_DATA; + } +} + +static UINT rdpdr_server_receive_io_lock_control_request(RdpdrServerContext* context, wStream* s, + UINT32 DeviceId, UINT32 FileId, + UINT32 CompletionId) +{ + UINT32 Operation = 0; + UINT32 Lock = 0; + UINT32 NumLocks = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Operation); + Stream_Read_UINT32(s, Lock); + Stream_Read_UINT32(s, NumLocks); + Stream_Seek(s, 20); /* Padding */ + + Lock &= 0x01; /* Only byte 0 is of importance */ + + for (UINT32 x = 0; x < NumLocks; x++) + { + UINT64 Length = 0; + UINT64 Offset = 0; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 16)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT64(s, Length); + Stream_Read_UINT64(s, Offset); + } + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.12 Server Drive Lock Control Request (DR_DRIVE_LOCK_REQ) " + "not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_device_io_request(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 DeviceId = 0; + UINT32 FileId = 0; + UINT32 CompletionId = 0; + UINT32 MajorFunction = 0; + UINT32 MinorFunction = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DeviceId); + Stream_Read_UINT32(s, FileId); + Stream_Read_UINT32(s, CompletionId); + Stream_Read_UINT32(s, MajorFunction); + Stream_Read_UINT32(s, MinorFunction); + if ((MinorFunction != 0) && (MajorFunction != IRP_MJ_DIRECTORY_CONTROL)) + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) MajorFunction=%s, " + "MinorFunction=0x%08" PRIx32 " != 0", + rdpdr_irp_string(MajorFunction), MinorFunction); + + switch (MajorFunction) + { + case IRP_MJ_CREATE: + return rdpdr_server_receive_io_create_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_CLOSE: + return rdpdr_server_receive_io_close_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_READ: + return rdpdr_server_receive_io_read_request(context, s, DeviceId, FileId, CompletionId); + case IRP_MJ_WRITE: + return rdpdr_server_receive_io_write_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_DEVICE_CONTROL: + return rdpdr_server_receive_io_device_control_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_QUERY_VOLUME_INFORMATION: + return rdpdr_server_receive_io_query_volume_information_request(context, s, DeviceId, + FileId, CompletionId); + case IRP_MJ_QUERY_INFORMATION: + return rdpdr_server_receive_io_query_information_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_SET_INFORMATION: + return rdpdr_server_receive_io_set_information_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_DIRECTORY_CONTROL: + return rdpdr_server_receive_io_directory_control_request(context, s, DeviceId, FileId, + CompletionId, MinorFunction); + case IRP_MJ_LOCK_CONTROL: + return rdpdr_server_receive_io_lock_control_request(context, s, DeviceId, FileId, + CompletionId); + default: + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, + "got DeviceId=0x%08" PRIx32 ", FileId=0x%08" PRIx32 + ", CompletionId=0x%08" PRIx32 ", MajorFunction=0x%08" PRIx32 + ", MinorFunction=0x%08" PRIx32, + DeviceId, FileId, CompletionId, MajorFunction, MinorFunction); + return ERROR_INVALID_DATA; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_io_completion(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 deviceId = 0; + UINT32 completionId = 0; + UINT32 ioStatus = 0; + RDPDR_IRP* irp = NULL; + UINT error = CHANNEL_RC_OK; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, deviceId); + Stream_Read_UINT32(s, completionId); + Stream_Read_UINT32(s, ioStatus); + WLog_Print(context->priv->log, WLOG_DEBUG, + "deviceId=%" PRIu32 ", completionId=0x%" PRIx32 ", ioStatus=0x%" PRIx32 "", deviceId, + completionId, ioStatus); + irp = rdpdr_server_dequeue_irp(context, completionId); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "IRP not found for completionId=0x%" PRIx32 "", + completionId); + return CHANNEL_RC_OK; + } + + /* Invoke the callback. */ + if (irp->Callback) + { + error = (*irp->Callback)(context, s, irp, deviceId, completionId, ioStatus); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_user_logged_on(RdpdrServerContext* context) +{ + wStream* s = NULL; + RDPDR_HEADER header = { 0 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_USER_LOGGEDON; + s = Stream_New(NULL, RDPDR_HEADER_LENGTH); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +static UINT rdpdr_server_receive_prn_cache_add_printer(RdpdrServerContext* context, wStream* s) +{ + char PortDosName[9] = { 0 }; + UINT32 PnPNameLen = 0; + UINT32 DriverNameLen = 0; + UINT32 PrinterNameLen = 0; + UINT32 CachedFieldsLen = 0; + const WCHAR* PnPName = NULL; + const WCHAR* DriverName = NULL; + const WCHAR* PrinterName = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 24)) + return ERROR_INVALID_DATA; + + Stream_Read(s, PortDosName, 8); + Stream_Read_UINT32(s, PnPNameLen); + Stream_Read_UINT32(s, DriverNameLen); + Stream_Read_UINT32(s, PrinterNameLen); + Stream_Read_UINT32(s, CachedFieldsLen); + + PnPName = rdpdr_read_ustring(context->priv->log, s, PnPNameLen); + if (!PnPName && (PnPNameLen > 0)) + return ERROR_INVALID_DATA; + DriverName = rdpdr_read_ustring(context->priv->log, s, DriverNameLen); + if (!DriverName && (DriverNameLen > 0)) + return ERROR_INVALID_DATA; + PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen); + if (!PrinterName && (PrinterNameLen > 0)) + return ERROR_INVALID_DATA; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, CachedFieldsLen)) + return ERROR_INVALID_DATA; + Stream_Seek(s, CachedFieldsLen); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.3 Add Printer Cachedata (DR_PRN_ADD_CACHEDATA) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_prn_cache_update_printer(RdpdrServerContext* context, wStream* s) +{ + UINT32 PrinterNameLen = 0; + UINT32 CachedFieldsLen = 0; + const WCHAR* PrinterName = NULL; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterNameLen); + Stream_Read_UINT32(s, CachedFieldsLen); + + PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen); + if (!PrinterName && (PrinterNameLen > 0)) + return ERROR_INVALID_DATA; + + const BYTE* config = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, CachedFieldsLen)) + return ERROR_INVALID_DATA; + Stream_Seek(s, CachedFieldsLen); + + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.4 Update Printer Cachedata (DR_PRN_UPDATE_CACHEDATA) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", config); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_prn_cache_delete_printer(RdpdrServerContext* context, wStream* s) +{ + UINT32 PrinterNameLen = 0; + const WCHAR* PrinterName = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterNameLen); + + PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen); + if (!PrinterName && (PrinterNameLen > 0)) + return ERROR_INVALID_DATA; + + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.5 Delete Printer Cachedata (DR_PRN_DELETE_CACHEDATA) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_prn_cache_rename_cachedata(RdpdrServerContext* context, wStream* s) +{ + UINT32 OldPrinterNameLen = 0; + UINT32 NewPrinterNameLen = 0; + const WCHAR* OldPrinterName = NULL; + const WCHAR* NewPrinterName = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OldPrinterNameLen); + Stream_Read_UINT32(s, NewPrinterNameLen); + + OldPrinterName = rdpdr_read_ustring(context->priv->log, s, OldPrinterNameLen); + if (!OldPrinterName && (OldPrinterNameLen > 0)) + return ERROR_INVALID_DATA; + NewPrinterName = rdpdr_read_ustring(context->priv->log, s, NewPrinterNameLen); + if (!NewPrinterName && (NewPrinterNameLen > 0)) + return ERROR_INVALID_DATA; + + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.6 Rename Printer Cachedata (DR_PRN_RENAME_CACHEDATA) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_prn_cache_data_request(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 EventId = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, EventId); + switch (EventId) + { + case RDPDR_ADD_PRINTER_EVENT: + return rdpdr_server_receive_prn_cache_add_printer(context, s); + case RDPDR_UPDATE_PRINTER_EVENT: + return rdpdr_server_receive_prn_cache_update_printer(context, s); + case RDPDR_DELETE_PRINTER_EVENT: + return rdpdr_server_receive_prn_cache_delete_printer(context, s); + case RDPDR_RENAME_PRINTER_EVENT: + return rdpdr_server_receive_prn_cache_rename_cachedata(context, s); + default: + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEPC] PAKID_PRN_CACHE_DATA unknown EventId=0x%08" PRIx32, EventId); + return ERROR_INVALID_DATA; + } +} + +static UINT rdpdr_server_receive_prn_using_xps_request(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 PrinterId = 0; + UINT32 Flags = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterId); + Stream_Read_UINT32(s, Flags); + + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.2 Server Printer Set XPS Mode (DR_PRN_USING_XPS) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "PrinterId=0x%08" PRIx32 ", Flags=0x%08" PRIx32, + PrinterId, Flags); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_pdu(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT error = ERROR_INVALID_DATA; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "receiving message {Component %s[%04" PRIx16 "], PacketId %s[%04" PRIx16 "]", + rdpdr_component_string(header->Component), header->Component, + rdpdr_packetid_string(header->PacketId), header->PacketId); + + if (header->Component == RDPDR_CTYP_CORE) + { + switch (header->PacketId) + { + case PAKID_CORE_SERVER_ANNOUNCE: + WLog_Print(context->priv->log, WLOG_ERROR, + "[MS-RDPEFS] 2.2.2.2 Server Announce Request " + "(DR_CORE_SERVER_ANNOUNCE_REQ) must not be sent by a client!"); + break; + + case PAKID_CORE_CLIENTID_CONFIRM: + error = rdpdr_server_receive_announce_response(context, s, header); + break; + + case PAKID_CORE_CLIENT_NAME: + error = rdpdr_server_receive_client_name_request(context, s, header); + if (error == CHANNEL_RC_OK) + error = rdpdr_server_send_core_capability_request(context); + if (error == CHANNEL_RC_OK) + error = rdpdr_server_send_client_id_confirm(context); + break; + + case PAKID_CORE_USER_LOGGEDON: + WLog_Print(context->priv->log, WLOG_ERROR, + "[MS-RDPEFS] 2.2.2.5 Server User Logged On (DR_CORE_USER_LOGGEDON) " + "must not be sent by a client!"); + break; + + case PAKID_CORE_SERVER_CAPABILITY: + WLog_Print(context->priv->log, WLOG_ERROR, + "[MS-RDPEFS] 2.2.2.7 Server Core Capability Request " + "(DR_CORE_CAPABILITY_REQ) must not be sent by a client!"); + break; + + case PAKID_CORE_CLIENT_CAPABILITY: + error = rdpdr_server_receive_core_capability_response(context, s, header); + if (error == CHANNEL_RC_OK) + { + if (context->priv->UserLoggedOnPdu) + error = rdpdr_server_send_user_logged_on(context); + } + + break; + + case PAKID_CORE_DEVICELIST_ANNOUNCE: + error = rdpdr_server_receive_device_list_announce_request(context, s, header); + break; + + case PAKID_CORE_DEVICELIST_REMOVE: + error = rdpdr_server_receive_device_list_remove_request(context, s, header); + break; + + case PAKID_CORE_DEVICE_REPLY: + WLog_Print(context->priv->log, WLOG_ERROR, + "[MS-RDPEFS] 2.2.2.1 Server Device Announce Response " + "(DR_CORE_DEVICE_ANNOUNCE_RSP) must not be sent by a client!"); + break; + + case PAKID_CORE_DEVICE_IOREQUEST: + error = rdpdr_server_receive_device_io_request(context, s, header); + break; + + case PAKID_CORE_DEVICE_IOCOMPLETION: + error = rdpdr_server_receive_device_io_completion(context, s, header); + break; + + default: + WLog_Print(context->priv->log, WLOG_WARN, + "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s", + rdpdr_component_string(header->Component), header->Component, + rdpdr_packetid_string(header->PacketId)); + break; + } + } + else if (header->Component == RDPDR_CTYP_PRN) + { + switch (header->PacketId) + { + case PAKID_PRN_CACHE_DATA: + error = rdpdr_server_receive_prn_cache_data_request(context, s, header); + break; + + case PAKID_PRN_USING_XPS: + error = rdpdr_server_receive_prn_using_xps_request(context, s, header); + break; + + default: + WLog_Print(context->priv->log, WLOG_WARN, + "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s", + rdpdr_component_string(header->Component), header->Component, + rdpdr_packetid_string(header->PacketId)); + break; + } + } + else + { + WLog_Print(context->priv->log, WLOG_WARN, + "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s", + rdpdr_component_string(header->Component), header->Component, + rdpdr_packetid_string(header->PacketId)); + } + + return IFCALLRESULT(error, context->ReceivePDU, context, header, error); +} + +static DWORD WINAPI rdpdr_server_thread(LPVOID arg) +{ + DWORD status = 0; + DWORD nCount = 0; + void* buffer = NULL; + HANDLE events[8] = { 0 }; + HANDLE ChannelEvent = NULL; + DWORD BytesReturned = 0; + UINT error = 0; + RdpdrServerContext* context = (RdpdrServerContext*)arg; + wStream* s = Stream_New(NULL, 4096); + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + if ((error = rdpdr_server_send_announce_request(context))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_send_announce_request failed with error %" PRIu32 "!", error); + goto out_stream; + } + + while (1) + { + size_t capacity = 0; + BytesReturned = 0; + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(context->priv->log, WLOG_ERROR, + "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + goto out_stream; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(context->priv->log, WLOG_ERROR, + "WaitForSingleObject failed with error %" PRIu32 "!", error); + goto out_stream; + } + + if (status == WAIT_OBJECT_0) + break; + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, NULL, 0, &BytesReturned)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + capacity = MIN(Stream_Capacity(s), ULONG_MAX); + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, (PCHAR)Stream_Buffer(s), + (ULONG)capacity, &BytesReturned)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (BytesReturned >= RDPDR_HEADER_LENGTH) + { + Stream_SetPosition(s, 0); + Stream_SetLength(s, BytesReturned); + + while (Stream_GetRemainingLength(s) >= RDPDR_HEADER_LENGTH) + { + RDPDR_HEADER header = { 0 }; + + Stream_Read_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Read_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + + if ((error = rdpdr_server_receive_pdu(context, s, &header))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_receive_pdu failed with error %" PRIu32 "!", error); + goto out_stream; + } + } + } + } + +out_stream: + Stream_Free(s, TRUE); +out: + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "rdpdr_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_start(RdpdrServerContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + context->priv->ChannelHandle = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RDPDR_SVC_CHANNEL_NAME); + + if (!context->priv->ChannelHandle) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelOpen failed!"); + return CHANNEL_RC_BAD_CHANNEL; + } + + if (!(context->priv->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = + CreateThread(NULL, 0, rdpdr_server_thread, (void*)context, 0, NULL))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "CreateThread failed!"); + CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_stop(RdpdrServerContext* context) +{ + UINT error = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (context->priv->StopEvent) + { + SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(context->priv->log, WLOG_ERROR, + "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + + CloseHandle(context->priv->Thread); + context->priv->Thread = NULL; + CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = NULL; + } + + if (context->priv->ChannelHandle) + { + WTSVirtualChannelClose(context->priv->ChannelHandle); + context->priv->ChannelHandle = NULL; + } + return CHANNEL_RC_OK; +} + +static void rdpdr_server_write_device_iorequest(wStream* s, UINT32 deviceId, UINT32 fileId, + UINT32 completionId, UINT32 majorFunction, + UINT32 minorFunction) +{ + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_DEVICE_IOREQUEST); /* PacketId (2 bytes) */ + Stream_Write_UINT32(s, deviceId); /* DeviceId (4 bytes) */ + Stream_Write_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Write_UINT32(s, completionId); /* CompletionId (4 bytes) */ + Stream_Write_UINT32(s, majorFunction); /* MajorFunction (4 bytes) */ + Stream_Write_UINT32(s, minorFunction); /* MinorFunction (4 bytes) */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_file_directory_information(wLog* log, wStream* s, + FILE_DIRECTORY_INFORMATION* fdi) +{ + UINT32 fileNameLength = 0; + WINPR_ASSERT(fdi); + ZeroMemory(fdi, sizeof(FILE_DIRECTORY_INFORMATION)); + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 64)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, fdi->NextEntryOffset); /* NextEntryOffset (4 bytes) */ + Stream_Read_UINT32(s, fdi->FileIndex); /* FileIndex (4 bytes) */ + Stream_Read_INT64(s, fdi->CreationTime.QuadPart); /* CreationTime (8 bytes) */ + Stream_Read_INT64(s, fdi->LastAccessTime.QuadPart); /* LastAccessTime (8 bytes) */ + Stream_Read_INT64(s, fdi->LastWriteTime.QuadPart); /* LastWriteTime (8 bytes) */ + Stream_Read_INT64(s, fdi->ChangeTime.QuadPart); /* ChangeTime (8 bytes) */ + Stream_Read_INT64(s, fdi->EndOfFile.QuadPart); /* EndOfFile (8 bytes) */ + Stream_Read_INT64(s, fdi->AllocationSize.QuadPart); /* AllocationSize (8 bytes) */ + Stream_Read_UINT32(s, fdi->FileAttributes); /* FileAttributes (4 bytes) */ + Stream_Read_UINT32(s, fileNameLength); /* FileNameLength (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, fileNameLength)) + return ERROR_INVALID_DATA; + + if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, fileNameLength / sizeof(WCHAR), fdi->FileName, + ARRAYSIZE(fdi->FileName)) < 0) + return ERROR_INVALID_DATA; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_create_request(RdpdrServerContext* context, UINT32 deviceId, + UINT32 completionId, const char* path, + UINT32 desiredAccess, UINT32 createOptions, + UINT32 createDisposition) +{ + size_t pathLength = 0; + wStream* s = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceCreateRequest: deviceId=%" PRIu32 + ", path=%s, desiredAccess=0x%" PRIx32 " createOptions=0x%" PRIx32 + " createDisposition=0x%" PRIx32 "", + deviceId, path, desiredAccess, createOptions, createDisposition); + /* Compute the required Unicode size. */ + pathLength = (strlen(path) + 1U) * sizeof(WCHAR); + s = Stream_New(NULL, 256U + pathLength); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, 0, completionId, IRP_MJ_CREATE, 0); + Stream_Write_UINT32(s, desiredAccess); /* DesiredAccess (4 bytes) */ + Stream_Write_UINT32(s, 0); /* AllocationSize (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Write_UINT32(s, 0); /* FileAttributes (4 bytes) */ + Stream_Write_UINT32(s, 3); /* SharedAccess (4 bytes) */ + Stream_Write_UINT32(s, createDisposition); /* CreateDisposition (4 bytes) */ + Stream_Write_UINT32(s, createOptions); /* CreateOptions (4 bytes) */ + WINPR_ASSERT(pathLength <= UINT32_MAX); + Stream_Write_UINT32(s, (UINT32)pathLength); /* PathLength (4 bytes) */ + /* Convert the path to Unicode. */ + if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path, + pathLength / sizeof(WCHAR), TRUE) < 0) + return ERROR_INTERNAL_ERROR; + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_close_request(RdpdrServerContext* context, UINT32 deviceId, + UINT32 fileId, UINT32 completionId) +{ + wStream* s = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceCloseRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 "", + deviceId, fileId); + s = Stream_New(NULL, 128); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_CLOSE, 0); + Stream_Zero(s, 32); /* Padding (32 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_read_request(RdpdrServerContext* context, UINT32 deviceId, + UINT32 fileId, UINT32 completionId, UINT32 length, + UINT32 offset) +{ + wStream* s = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceReadRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 + ", length=%" PRIu32 ", offset=%" PRIu32 "", + deviceId, fileId, length, offset); + s = Stream_New(NULL, 128); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_READ, 0); + Stream_Write_UINT32(s, length); /* Length (4 bytes) */ + Stream_Write_UINT32(s, offset); /* Offset (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Zero(s, 20); /* Padding (20 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_write_request(RdpdrServerContext* context, UINT32 deviceId, + UINT32 fileId, UINT32 completionId, + const char* data, UINT32 length, UINT32 offset) +{ + wStream* s = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceWriteRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 + ", length=%" PRIu32 ", offset=%" PRIu32 "", + deviceId, fileId, length, offset); + s = Stream_New(NULL, 64 + length); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_WRITE, 0); + Stream_Write_UINT32(s, length); /* Length (4 bytes) */ + Stream_Write_UINT32(s, offset); /* Offset (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Zero(s, 20); /* Padding (20 bytes) */ + Stream_Write(s, data, length); /* WriteData (variable) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_query_directory_request(RdpdrServerContext* context, + UINT32 deviceId, UINT32 fileId, + UINT32 completionId, const char* path) +{ + size_t pathLength = 0; + wStream* s = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceQueryDirectoryRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 + ", path=%s", + deviceId, fileId, path); + /* Compute the required Unicode size. */ + pathLength = path ? (strlen(path) + 1) * sizeof(WCHAR) : 0; + s = Stream_New(NULL, 64 + pathLength); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_DIRECTORY_CONTROL, + IRP_MN_QUERY_DIRECTORY); + Stream_Write_UINT32(s, FileDirectoryInformation); /* FsInformationClass (4 bytes) */ + Stream_Write_UINT8(s, path ? 1 : 0); /* InitialQuery (1 byte) */ + WINPR_ASSERT(pathLength <= UINT32_MAX); + Stream_Write_UINT32(s, (UINT32)pathLength); /* PathLength (4 bytes) */ + Stream_Zero(s, 23); /* Padding (23 bytes) */ + + /* Convert the path to Unicode. */ + if (pathLength > 0) + { + if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path, + pathLength / sizeof(WCHAR), TRUE) < 0) + return ERROR_INTERNAL_ERROR; + } + + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_file_rename_request(RdpdrServerContext* context, + UINT32 deviceId, UINT32 fileId, + UINT32 completionId, const char* path) +{ + size_t pathLength = 0; + wStream* s = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceFileNameRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 + ", path=%s", + deviceId, fileId, path); + /* Compute the required Unicode size. */ + pathLength = path ? (strlen(path) + 1) * sizeof(WCHAR) : 0; + s = Stream_New(NULL, 64 + pathLength); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_SET_INFORMATION, + 0); + Stream_Write_UINT32(s, FileRenameInformation); /* FsInformationClass (4 bytes) */ + WINPR_ASSERT(pathLength <= UINT32_MAX - 6U); + Stream_Write_UINT32(s, (UINT32)pathLength + 6U); /* Length (4 bytes) */ + Stream_Zero(s, 24); /* Padding (24 bytes) */ + /* RDP_FILE_RENAME_INFORMATION */ + Stream_Write_UINT8(s, 0); /* ReplaceIfExists (1 byte) */ + Stream_Write_UINT8(s, 0); /* RootDirectory (1 byte) */ + Stream_Write_UINT32(s, (UINT32)pathLength); /* FileNameLength (4 bytes) */ + + /* Convert the path to Unicode. */ + if (pathLength > 0) + { + if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path, + pathLength / sizeof(WCHAR), TRUE) < 0) + return ERROR_INTERNAL_ERROR; + } + + return rdpdr_seal_send_free_request(context, s); +} + +static void rdpdr_server_convert_slashes(char* path, int size) +{ + WINPR_ASSERT(path || (size <= 0)); + + for (int i = 0; (i < size) && (path[i] != '\0'); i++) + { + if (path[i] == '/') + path[i] = '\\'; + } +} + +/************************************************* + * Drive Create Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(irp); + WINPR_UNUSED(s); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveCreateDirectoryCallback2: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + /* Invoke the create directory completion routine. */ + context->OnDriveCreateDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId = 0; + UINT8 information = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveCreateDirectoryCallback1: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the create directory completion routine. */ + context->OnDriveCreateDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_create_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(callbackData); + WINPR_ASSERT(path); + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_create_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request( + context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATE); +} + +/************************************************* + * Drive Delete Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_UNUSED(s); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveDeleteDirectoryCallback2: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + /* Invoke the delete directory completion routine. */ + context->OnDriveDeleteDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId = 0; + UINT8 information = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveDeleteDirectoryCallback1: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the delete directory completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request( + context, deviceId, irp->CompletionId, irp->PathName, DELETE | SYNCHRONIZE, + FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Query Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT error = 0; + UINT32 length = 0; + FILE_DIRECTORY_INFORMATION fdi = { 0 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveQueryDirectoryCallback2: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (length > 0) + { + if ((error = rdpdr_server_read_file_directory_information(context->priv->log, s, &fdi))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_read_file_directory_information failed with error %" PRIu32 + "!", + error); + return error; + } + } + else + { + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Seek(s, 1); /* Padding (1 byte) */ + } + + if (ioStatus == STATUS_SUCCESS) + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, + length > 0 ? &fdi : NULL); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback2; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to query the directory. */ + return rdpdr_server_send_device_query_directory_request(context, irp->DeviceId, irp->FileId, + irp->CompletionId, NULL); + } + else + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, NULL); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveQueryDirectoryCallback1: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, NULL); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, fileId); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + winpr_str_append("\\*.*", irp->PathName, ARRAYSIZE(irp->PathName), NULL); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to query the directory. */ + return rdpdr_server_send_device_query_directory_request(context, deviceId, fileId, + irp->CompletionId, irp->PathName); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + return rdpdr_server_send_device_create_request( + context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Open File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_open_file_callback(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId = 0; + UINT8 information = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveOpenFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Invoke the open file completion routine. */ + context->OnDriveOpenFileComplete(context, irp->CallbackData, ioStatus, deviceId, fileId); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_open_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path, UINT32 desiredAccess, + UINT32 createDisposition) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_open_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request(context, deviceId, irp->CompletionId, + irp->PathName, desiredAccess | SYNCHRONIZE, + FILE_SYNCHRONOUS_IO_NONALERT, createDisposition); +} + +/************************************************* + * Drive Read File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_read_file_callback(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 length = 0; + char* buffer = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveReadFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, length)) + return ERROR_INVALID_DATA; + + if (length > 0) + { + buffer = Stream_Pointer(s); + Stream_Seek(s, length); + } + + /* Invoke the read file completion routine. */ + context->OnDriveReadFileComplete(context, irp->CallbackData, ioStatus, buffer, length); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_read_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, UINT32 fileId, UINT32 length, + UINT32 offset) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_read_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp + return rdpdr_server_send_device_read_request(context, deviceId, fileId, irp->CompletionId, + length, offset); +} + +/************************************************* + * Drive Write File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_write_file_callback(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 length = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveWriteFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + Stream_Seek(s, 1); /* Padding (1 byte) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, length)) + return ERROR_INVALID_DATA; + + /* Invoke the write file completion routine. */ + context->OnDriveWriteFileComplete(context, irp->CallbackData, ioStatus, length); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_write_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, UINT32 fileId, const char* buffer, + UINT32 length, UINT32 offset) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_write_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp + return rdpdr_server_send_device_write_request(context, deviceId, fileId, irp->CompletionId, + buffer, length, offset); +} + +/************************************************* + * Drive Close File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_close_file_callback(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_UNUSED(s); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveCloseFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + // padding 5 bytes + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Seek(s, 5); + + /* Invoke the close file completion routine. */ + context->OnDriveCloseFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_close_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, UINT32 fileId) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_close_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp + return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId); +} + +/************************************************* + * Drive Delete File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_UNUSED(s); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveDeleteFileCallback2: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + /* Invoke the delete file completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId = 0; + UINT8 information = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveDeleteFileCallback1: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the close file completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_file_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_file_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request( + context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE, + FILE_DELETE_ON_CLOSE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Rename File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback3(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_UNUSED(context); + WINPR_UNUSED(s); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveRenameFileCallback3: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 length = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveRenameFileCallback2: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + Stream_Seek(s, 1); /* Padding (1 byte) */ + /* Invoke the rename file completion routine. */ + context->OnDriveRenameFileComplete(context, irp->CallbackData, ioStatus); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback3; + irp->DeviceId = deviceId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, irp->FileId, + irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 fileId = 0; + UINT8 information = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveRenameFileCallback1: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the rename file completion routine. */ + context->OnDriveRenameFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Read_UINT8(s, information); /* Information (1 byte) */ + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to rename the file */ + return rdpdr_server_send_device_file_rename_request(context, deviceId, fileId, + irp->CompletionId, irp->ExtraBuffer); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* oldPath, + const char* newPath) +{ + RDPDR_IRP* irp = NULL; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, oldPath, sizeof(irp->PathName) - 1); + strncpy(irp->ExtraBuffer, newPath, sizeof(irp->ExtraBuffer) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + rdpdr_server_convert_slashes(irp->ExtraBuffer, sizeof(irp->ExtraBuffer)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp + return rdpdr_server_send_device_create_request(context, deviceId, irp->CompletionId, + irp->PathName, FILE_READ_DATA | SYNCHRONIZE, + FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +static void rdpdr_server_private_free(RdpdrServerPrivate* ctx) +{ + if (!ctx) + return; + ListDictionary_Free(ctx->IrpList); + HashTable_Free(ctx->devicelist); + free(ctx->ClientComputerName); + free(ctx); +} + +#define TAG CHANNELS_TAG("rdpdr.server") +static RdpdrServerPrivate* rdpdr_server_private_new(void) +{ + RdpdrServerPrivate* priv = (RdpdrServerPrivate*)calloc(1, sizeof(RdpdrServerPrivate)); + + if (!priv) + goto fail; + + priv->log = WLog_Get(TAG); + priv->VersionMajor = RDPDR_VERSION_MAJOR; + priv->VersionMinor = RDPDR_VERSION_MINOR_RDP6X; + priv->ClientId = g_ClientId++; + priv->UserLoggedOnPdu = TRUE; + priv->NextCompletionId = 1; + priv->IrpList = ListDictionary_New(TRUE); + + if (!priv->IrpList) + goto fail; + + priv->devicelist = HashTable_New(FALSE); + if (!priv->devicelist) + goto fail; + + HashTable_SetHashFunction(priv->devicelist, rdpdr_deviceid_hash); + wObject* obj = HashTable_ValueObject(priv->devicelist); + WINPR_ASSERT(obj); + obj->fnObjectFree = rdpdr_device_free_h; + obj->fnObjectNew = rdpdr_device_clone; + + return priv; +fail: + rdpdr_server_private_free(priv); + return NULL; +} + +RdpdrServerContext* rdpdr_server_context_new(HANDLE vcm) +{ + RdpdrServerContext* context = (RdpdrServerContext*)calloc(1, sizeof(RdpdrServerContext)); + + if (!context) + goto fail; + + context->vcm = vcm; + context->Start = rdpdr_server_start; + context->Stop = rdpdr_server_stop; + context->DriveCreateDirectory = rdpdr_server_drive_create_directory; + context->DriveDeleteDirectory = rdpdr_server_drive_delete_directory; + context->DriveQueryDirectory = rdpdr_server_drive_query_directory; + context->DriveOpenFile = rdpdr_server_drive_open_file; + context->DriveReadFile = rdpdr_server_drive_read_file; + context->DriveWriteFile = rdpdr_server_drive_write_file; + context->DriveCloseFile = rdpdr_server_drive_close_file; + context->DriveDeleteFile = rdpdr_server_drive_delete_file; + context->DriveRenameFile = rdpdr_server_drive_rename_file; + context->priv = rdpdr_server_private_new(); + if (!context->priv) + goto fail; + + /* By default announce everything, the server application can deactivate that later on */ + context->supported = UINT16_MAX; + + return context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + rdpdr_server_context_free(context); + WINPR_PRAGMA_DIAG_POP + return NULL; +} + +void rdpdr_server_context_free(RdpdrServerContext* context) +{ + if (!context) + return; + + rdpdr_server_private_free(context->priv); + free(context); +} diff --git a/channels/rdpdr/server/rdpdr_main.h b/channels/rdpdr/server/rdpdr_main.h new file mode 100644 index 0000000..dabbae2 --- /dev/null +++ b/channels/rdpdr/server/rdpdr_main.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel Extension + * + * Copyright 2014 Dell Software <Mike.McDonald@software.dell.com> + * Copyright 2013 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. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H + +#include <winpr/collections.h> +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/thread.h> + +#include <freerdp/settings.h> +#include <freerdp/server/rdpdr.h> + +typedef struct S_RDPDR_IRP +{ + UINT32 CompletionId; + UINT32 DeviceId; + UINT32 FileId; + char PathName[256]; + char ExtraBuffer[256]; + void* CallbackData; + UINT(*Callback) + (RdpdrServerContext* context, wStream* s, struct S_RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus); +} RDPDR_IRP; + +#endif /* FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H */ |