/*++ Copyright (c) 1998-2000 Microsoft Corporation Module Name : rdpdrprt.c Abstract: Manage dynamic printer port allocation for the RDP device redirection kernel mode component, rdpdr.sys. Port number 0 is reserved and is never allocated. Author: tadb Revision History: --*/ #include "precomp.hxx" #define TRC_FILE "rdpdrprt" #include "trc.h" #define DRIVER #include #ifdef __cplusplus extern "C" { #endif #include "cfg.h" #include "pnp.h" //////////////////////////////////////////////////////////////////////// // // Function Prototypes // // Generate a port name from a port number. BOOL GeneratePortName( IN ULONG portNumber, OUT PWSTR portName ); // Finds the next free port, following the last port checked, in the port bit // array. NTSTATUS FindNextFreePortInPortArray( IN ULONG lastPortChecked, OUT ULONG *nextFreePort ); // Increase the size of the port bit array to hold at least one new // port. NTSTATUS IncreasePortBitArraySize(); // Format a port description. void GeneratePortDescription( IN PCSTR dosPortName, IN PCWSTR clientName, IN PWSTR description ); // Return TRUE if the port's device interface is usable, from dynamon's // perspective. BOOL PortIsAvailableForUse( IN HANDLE regHandle ); // Set the port description string to disabled. NTSTATUS SetPortDescrToDisabled( IN PUNICODE_STRING symbolicLinkName ); // Clean up ports registered in a previous boot. NTSTATUS CleanUpExistingPorts(); // Allocate a port. NTSTATUS AllocatePrinterPort( IN ULONG portArrayIndex, OUT PWSTR portName, OUT ULONG *portNumber, OUT HANDLE *regHandle, OUT PUNICODE_STRING symbolicLinkName ); #ifdef __cplusplus } // extern "C" #endif //////////////////////////////////////////////////////////////////////// // // Defines and Macros // #define BITSINBYTE 8 #define BITSINULONG (sizeof(ULONG) * BITSINBYTE) // Printer port description length (in characters). #define RDPDRPRT_PORTDESCRLENGTH \ MAX_COMPUTERNAME_LENGTH + 1 + 2 + PREFERRED_DOS_NAME_SIZE + 1 // Computer Name ':' ' ' Dos Name Terminator // Initial size of the port bit array. #define RDPDRPRT_INITIALPORTCOUNT 64 //restore this#define RDPDRPRT_INITIALPORTCOUNT 256 // Base name for TermSrv printer ports #define RDPDRPRT_BASEPORTNAME L"TS" // Device interface port number registry value name. #define PORT_NUM_VALUE_NAME L"Port Number" // Device interface write size registry value name. #define PORT_WRITESIZE_VALUE_NAME L"MaxBufferSize" // Device interface port base name registry value name. #define PORT_BASE_VALUE_NAME L"Base Name" // Device interface port description registry value name. #define PORT_DESCR_VALUE_NAME L"Port Description" // Device interface port recyclable flag registry value name. #define RECYCLABLE_FLAG_VALUE_NAME L"recyclable" // We will use separate arrays for the printer port and printer queues to avoid // a race condition between creating a printer queue and listening on the printer port #define PRINTER_PORT_ARRAY_ID 1 //////////////////////////////////////////////////////////////////////// // // External Globals // // The Physical Device Object that terminates our DO stack (defined in rdpdyn.c). extern PDEVICE_OBJECT RDPDYN_PDO; // USBMON Port Write Size. Need to keep it under 64k for 16-bit clients ... // otherwise, the go off the end of a segment. (defined in rdpdr.cpp) extern ULONG PrintPortWriteSize; //////////////////////////////////////////////////////////////////////// // // Globals for this Module // // // For Tracking Allocated Ports. A cleared bit indicates a free port. // ULONG *PortBitArray = NULL; ULONG PortBitArraySizeInBytes = 0; // // Is this module initialized? // BOOL RDPDRPRT_Initialized = FALSE; // Description for disabled port. Note: We can localize this later. WCHAR DisabledPortDescription[RDPDRPRT_PORTDESCRLENGTH] = L"Inactive TS Port"; ULONG DisabledPortDescrSize = 0; // This is the GUID we use to identify a dynamic printer port to // dynamon. // {28D78FAD-5A12-11d1-AE5B-0000F803A8C2} const GUID DYNPRINT_GUID = { 0x28d78fad, 0x5a12, 0x11d1, { 0xae, 0x5b, 0x0, 0x0, 0xf8, 0x3, 0xa8, 0xc2 } }; // // Port Allocation Lock // KSPIN_LOCK PortAllocLock; KIRQL OldIRQL; #define RDPDRPRT_LOCK() \ KeAcquireSpinLock(&PortAllocLock, &OldIRQL) #define RDPDRPRT_UNLOCK() \ KeReleaseSpinLock(&PortAllocLock, OldIRQL) NTSTATUS RDPDRPRT_Initialize( ) /*++ Routine Description: Initialize this module. Arguments: Return Value: STATUS_SUCCESS if successful, STATUS_UNSUCCESSFUL otherwise --*/ { NTSTATUS status = STATUS_SUCCESS; BEGIN_FN("RDPDRPRT_Initialize"); // // Initialize the lock for port allocations. // KeInitializeSpinLock(&PortAllocLock); // // Compute the size of the disabled port description string. // DisabledPortDescrSize = (wcslen(DisabledPortDescription)+1)*sizeof(WCHAR); // // Allocate and initialize the allocated port bits array. // // Calculate initial size. PortBitArraySizeInBytes = (RDPDRPRT_INITIALPORTCOUNT / BITSINULONG) * sizeof(ULONG); if (RDPDRPRT_INITIALPORTCOUNT % BITSINULONG) { PortBitArraySizeInBytes += sizeof(ULONG); } // Allocate. PortBitArray = (ULONG *)new(NonPagedPool) BYTE[PortBitArraySizeInBytes]; if (PortBitArray == NULL) { TRC_ERR((TB, "Error allocating %ld bytes for port array", PortBitArraySizeInBytes)); PortBitArraySizeInBytes = 0; status = STATUS_INSUFFICIENT_RESOURCES; } else { // Initially, all the ports are free. RtlZeroMemory(PortBitArray, PortBitArraySizeInBytes); } // // Clean up ports allocated in a previous boot. // if (status == STATUS_SUCCESS) { // // Cleanup status not critical for init // CleanUpExistingPorts(); RDPDRPRT_Initialized = TRUE; } return status; } void RDPDRPRT_Shutdown() /*++ Routine Description: Shut down this module. Arguments: Return Value: --*/ { BEGIN_FN("RDPDRPRT_Shutdown"); if (!RDPDRPRT_Initialized) { TRC_ERR((TB, "RDPDRPRT_Shutdown: RDPDRPRT is not initialized. Exiting.")); return; } // // Release the allocated port bits array. // RDPDRPRT_LOCK(); if (PortBitArray != NULL) { delete PortBitArray; #ifdef DBG PortBitArray = NULL; PortBitArraySizeInBytes = 0; #endif } RDPDRPRT_UNLOCK(); } NTSTATUS RDPDRPRT_RegisterPrinterPortInterface( IN PWSTR clientMachineName, IN PCSTR clientPortName, IN PUNICODE_STRING clientDevicePath, OUT PWSTR portName, IN OUT PUNICODE_STRING symbolicLinkName, OUT ULONG *portNumber ) /*++ Routine Description: Register a new client-side port with the spooler via the dynamic port monitor. Arguments: clientMachineName - Client computer name for port description. clientPortName - Client-side port name for port description. clientDevicePath - Server-side device path for the port. Reads and writes to this device are reads and writes to the client-side device. portName - What we end up naming the port. symbolicLinkName - Symbolic link device name for port being registered. portNumber - Port number for port being registered. Return Value: STATUS_SUCCESS if successful, STATUS_UNSUCCESSFUL otherwise --*/ { WCHAR portDesc[RDPDRPRT_PORTDESCRLENGTH]; NTSTATUS status; UNICODE_STRING unicodeStr; HANDLE hInterfaceKey = INVALID_HANDLE_VALUE; BOOL symbolicLinkNameAllocated; BOOL isPrinterPort = FALSE; ULONG portArrayIndex = 0; ULONG len = 0; PSTR tempName = NULL; BEGIN_FN("RDPDRPRT_RegisterPrinterPortInterface"); TRC_NRM((TB, "Device path %wZ", clientDevicePath)); if (!RDPDRPRT_Initialized) { TRC_ERR((TB, "RDPDRPRT_RegisterPrinterPortInterface:" "RDPDRPRT is not initialized. Exiting.")); return STATUS_INVALID_DEVICE_STATE; } // // Find if this is a printer port or printer name. // First make a copy of the passed in port name // and convert to upper case. // if (clientPortName != NULL) { len = strlen(clientPortName); tempName = (PSTR)new(NonPagedPool) CHAR[len + 1]; if (tempName != NULL) { PSTR temp = tempName; strcpy(tempName, clientPortName); while (len--) { if (*tempName <= 'z' && *tempName >= 'a') { (*tempName) ^= 0x20; tempName++; } } // // Search for the substring ports // isPrinterPort = strstr(temp, "LPT") || strstr(temp, "COM"); // // If printer port, we use a specific array index // if (isPrinterPort) { portArrayIndex = PRINTER_PORT_ARRAY_ID; } status = STATUS_SUCCESS; } else { TRC_ERR((TB, "Error allocating %ld bytes for tempName", len+1)); status = STATUS_INSUFFICIENT_RESOURCES; } } else { status = STATUS_INVALID_PARAMETER; } // // Allocate a port. // if (NT_SUCCESS(status)) { status = AllocatePrinterPort(portArrayIndex, portName, portNumber, &hInterfaceKey, symbolicLinkName); symbolicLinkNameAllocated = (status == STATUS_SUCCESS); } // // Add the port number to the device interface key. // if (NT_SUCCESS(status)) { RtlInitUnicodeString(&unicodeStr, PORT_NUM_VALUE_NAME); status=ZwSetValueKey(hInterfaceKey, &unicodeStr, 0, REG_DWORD, portNumber, sizeof(ULONG)); if (!NT_SUCCESS(status)) { TRC_ERR((TB, "ZwSetValueKey failed: 08X.", status)); } } // // Add the port name base component to the device interface key. // This identifies us as a TS port. // if (NT_SUCCESS(status)) { RtlInitUnicodeString(&unicodeStr, PORT_BASE_VALUE_NAME); status=ZwSetValueKey( hInterfaceKey, &unicodeStr, 0, REG_SZ, RDPDRPRT_BASEPORTNAME, sizeof(RDPDRPRT_BASEPORTNAME) ); if (!NT_SUCCESS(status)) { TRC_ERR((TB, "ZwSetValueKey failed with status %08X.", status)); } } // // Add the port description string. // if (NT_SUCCESS(status)) { GeneratePortDescription( clientPortName, clientMachineName, portDesc ); RtlInitUnicodeString(&unicodeStr, PORT_DESCR_VALUE_NAME); status=ZwSetValueKey(hInterfaceKey, &unicodeStr, 0, REG_SZ, portDesc, (wcslen(portDesc)+1)*sizeof(WCHAR)); if (!NT_SUCCESS(status)) { TRC_ERR((TB, "ZwSetValueKey failed with status %08X.", status)); } } // // Add Port 'Write Size' Field. This is the size of writes sent by USBMON.DLL. // if (NT_SUCCESS(status)) { RtlInitUnicodeString(&unicodeStr, PORT_WRITESIZE_VALUE_NAME); status=ZwSetValueKey(hInterfaceKey, &unicodeStr, 0, REG_DWORD, &PrintPortWriteSize, sizeof(ULONG)); if (!NT_SUCCESS(status)) { TRC_ERR((TB, "ZwSetValueKey failed: 08X.", status)); } } // // Associate the client device path with the device interface so we can // reparse back to the correct client device on IRP_MJ_CREATE. // if (NT_SUCCESS(status)) { RtlInitUnicodeString(&unicodeStr, CLIENT_DEVICE_VALUE_NAME); status=ZwSetValueKey(hInterfaceKey, &unicodeStr, 0, REG_SZ, clientDevicePath->Buffer, (wcslen(clientDevicePath->Buffer)+1)*sizeof(WCHAR)); if (!NT_SUCCESS(status)) { TRC_ERR((TB, "ZwSetValueKey failed with status %08X.", status)); } } // // Make sure the changes made it to disk in case we have a hard reboot. // if (NT_SUCCESS(status)) { status = ZwFlushKey(hInterfaceKey); if (!NT_SUCCESS(status)) { TRC_ERR((TB, "ZwFlushKey failed with status %08X.", status)); } } // // Enable the interface. // if (NT_SUCCESS(status)) { status=IoSetDeviceInterfaceState(symbolicLinkName, TRUE); if (!NT_SUCCESS(status)) { TRC_ERR((TB, "IoSetDeviceInterfaceState failed with status %08X.", status)); } } // If we failed, then delete the symbolic link name. if (!NT_SUCCESS(status) && symbolicLinkNameAllocated) { RtlFreeUnicodeString(symbolicLinkName); } if (hInterfaceKey != INVALID_HANDLE_VALUE) { ZwClose(hInterfaceKey); } if (tempName != NULL) { delete tempName; } TRC_NRM((TB, "returning port number %ld.", *portNumber)); return status; } void RDPDRPRT_UnregisterPrinterPortInterface( IN ULONG portNumber, IN PUNICODE_STRING symbolicLinkName ) /*++ Routine Description: Unregister a port registered via call to RDPDRPRT_RegisterPrinterPortInterface Arguments: portNumber - Port number returned by RDPDRPRT_RegisterPrinterPortInterface. symbolicLinkName - Symbolic link device name returned by RDPDRPRT_RegisterPrinterPortInterface. Return Value: NA --*/ { ULONG ofs, bit; #if DBG NTSTATUS status; #endif BEGIN_FN("RDPDRPRT_UnregisterPrinterPortInterface"); if (!RDPDRPRT_Initialized) { TRC_ERR((TB, "RDPDRPRT_UnregisterPrinterPortInterface:" "RDPDRPRT is not initialized. Exiting.")); return; } TRC_ASSERT(symbolicLinkName != NULL, (TB, "symbolicLinkName != NULL")); TRC_ASSERT(symbolicLinkName->Buffer != NULL, (TB, "symbolicLinkName->Buffer != NULL")); TRC_NRM((TB, "Disabling port %ld with interface %wZ", portNumber, symbolicLinkName)); // // Change the port description to disabled. // SetPortDescrToDisabled(symbolicLinkName); // // Disable the device interface, which effectively hides the port from the // port monitor. // #if DBG status = IoSetDeviceInterfaceState(symbolicLinkName, FALSE); if (status != STATUS_SUCCESS) { TRC_NRM((TB, "IoSetDeviceInterfaceState returned error %08X", status)); } #else IoSetDeviceInterfaceState(symbolicLinkName, FALSE); #endif // // Release the symbolic link name. // RtlFreeUnicodeString(symbolicLinkName); // // Indicate that the port is no longer in use in the port bit array. // RDPDRPRT_LOCK(); ofs = portNumber / BITSINULONG; bit = portNumber % BITSINULONG; PortBitArray[ofs] &= ~(1<= (currentArraySize)) { status = IncreasePortBitArraySize(); if (status == STATUS_SUCCESS) { currentArraySize = PortBitArraySizeInBytes/sizeof(ULONG); } else { RDPDRPRT_UNLOCK(); return status; } } // // Find the next free bit. // while (1) { // // If the current port is already allocated.. // if (PortBitArray[ofs] & (1<= BITSINULONG) { bit = 0; ofs += 2; } // // See if we need to size the port bit array. // if (ofs >= (currentArraySize)) { status = IncreasePortBitArraySize(); if (status == STATUS_SUCCESS) { currentArraySize = PortBitArraySizeInBytes/sizeof(ULONG); } else { break; } } } else { // // Mark the port used. // PortBitArray[ofs] |= (1<0; digitsInPortNumber++,tmp/=10); // // Make sure we don't exceed the maximum digits allowed in a port name. // if (digitsInPortNumber > RDPDR_PORTNAMEDIGITS) { TRC_ASSERT(FALSE,(TB, "Maximum digits in port exceeded.")); return FALSE; } // // Copy the port name base.. // wcscpy(portName, RDPDRPRT_BASEPORTNAME); baseLen = (sizeof(RDPDRPRT_BASEPORTNAME)/sizeof(WCHAR))-1; // // Convert the port number to a unicode string. // numericUnc.Length = 0; numericUnc.MaximumLength = (RDPDR_PORTNAMEDIGITS+1) * sizeof(WCHAR); numericUnc.Buffer = numericBuf; RtlIntegerToUnicodeString(portNumber, 10, &numericUnc); // // If we need to pad the port number. // if (RDPDR_PORTNAMEDIGITSTOPAD > digitsInPortNumber) { toPad = RDPDR_PORTNAMEDIGITSTOPAD - digitsInPortNumber; // // Pad. // for (i=0; i 0) { TRC_NRM((TB, "CleanUpExistingPorts disabling %ws...", symbolicLink)); // // Open the registry key for the device interface. // RtlInitUnicodeString(&unicodeStr, symbolicLink); status = IoOpenDeviceInterfaceRegistryKey( &unicodeStr, KEY_ALL_ACCESS, &deviceInterfaceKey ); // // Make sure the port description has been set to "disabled" // if (status == STATUS_SUCCESS) { RtlInitUnicodeString(&unicodeStr, PORT_DESCR_VALUE_NAME); ZwSetValueKey(deviceInterfaceKey, &unicodeStr, 0, REG_SZ, DisabledPortDescription, DisabledPortDescrSize); } else { TRC_ERR((TB, "Unable to open device interface: %08X", status)); // Remember that the open failed. deviceInterfaceKey = INVALID_HANDLE_VALUE; } // // See if the recyclable flag has been set by dynamon.dll. If it has // been, then the port is deletable. // if (status == STATUS_SUCCESS) { RtlInitUnicodeString(&unicodeStr, RECYCLABLE_FLAG_VALUE_NAME); status = ZwQueryValueKey( deviceInterfaceKey, &unicodeStr, KeyValueBasicInformation, (PVOID)basicValueInformation, sizeof(basicValueInformation), &bytesReturned ); } // // Delete the port number value if it exists. Don't care about the // return value because it just means that this port cannot be reused // by us and this is not a critical error. // if (status == STATUS_SUCCESS) { RtlInitUnicodeString(&unicodeStr, PORT_NUM_VALUE_NAME); ZwDeleteValueKey(deviceInterfaceKey, &unicodeStr); } else { TRC_ERR((TB, "CleanUpExistingPorts recyclable flag not set")); } // // Close the registry key. // if (deviceInterfaceKey != INVALID_HANDLE_VALUE) { ZwClose(deviceInterfaceKey); } // // Move to the next symbolic link in the list. // symbolicLink += (len+1); len = wcslen(symbolicLink); } // // Release the symbolic link list. // if (symbolicLinkList != NULL) { ExFreePool(symbolicLinkList); } } else { TRC_NRM((TB, "IoGetDeviceInterfaces failed with status %08X", returnStatus)); } return returnStatus; }