You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1309 lines
37 KiB
1309 lines
37 KiB
/*++
|
|
|
|
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 <stdio.h>
|
|
#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<<bit);
|
|
RDPDRPRT_UNLOCK();
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
FindNextFreePortInPortArray(
|
|
IN ULONG lastPortChecked,
|
|
OUT ULONG *nextFreePort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finds the next free port, following the last port checked, in the port bit
|
|
array. The status of the port is changed to "in use" prior to this function
|
|
returning.
|
|
|
|
Arguments:
|
|
|
|
lastPortChecked - Last port number checked. Should be 0 if first time
|
|
called.
|
|
nextFreePort - Next free port number.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if successful,
|
|
STATUS_UNSUCCESSFUL otherwise
|
|
|
|
--*/
|
|
{
|
|
ULONG currentPort;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
ULONG ofs, bit;
|
|
ULONG currentArraySize;
|
|
|
|
BEGIN_FN("FindNextFreePortInPortArray");
|
|
|
|
//
|
|
// Find the offset of the long word for the port into the port array
|
|
// and the offset for the bit into the long word.
|
|
//
|
|
ofs = (lastPortChecked+1) / BITSINULONG;
|
|
bit = (lastPortChecked+1) % BITSINULONG;
|
|
//
|
|
// If we re-entered this function (because from Dynamon's perspective, the port is not available),
|
|
// it is possible that the lastPortChecked was at the array boundary (ex: 31, 63, 95, 127, ...etc).
|
|
// In this case, the offset will mess up the array separation.
|
|
// So, we need to adjust the offset to the next higher one
|
|
// to keep the printer queue array and the printer port arrays separately.
|
|
//
|
|
if (bit == 0) {
|
|
ofs += 1;
|
|
}
|
|
|
|
RDPDRPRT_LOCK();
|
|
|
|
//
|
|
// If we need to size the port array.
|
|
// Note : We have two arrays - one for the printer ports and one for printer queues
|
|
// This is to avoid a race condition between creating a printer queue and listening on the printer port
|
|
//
|
|
currentArraySize = PortBitArraySizeInBytes/sizeof(ULONG);
|
|
if (ofs >= (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<<bit)) {
|
|
//
|
|
// Next bit.
|
|
//
|
|
bit += 1;
|
|
if (bit >= 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<<bit);
|
|
|
|
//
|
|
// Return the free port.
|
|
//
|
|
*nextFreePort = (ofs * BITSINULONG) + bit;
|
|
TRC_NRM((TB, "next free port is %ld", *nextFreePort));
|
|
break;
|
|
}
|
|
}
|
|
|
|
RDPDRPRT_UNLOCK();
|
|
|
|
TRC_NRM((TB, "return status %08X", status));
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
IncreasePortBitArraySize(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Increase the size of the port bit array to hold at least one new
|
|
port.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if successful,
|
|
STATUS_UNSUCCESSFUL otherwise
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
ULONG newPortBitArraySize;
|
|
ULONG *newPortBitArray;
|
|
|
|
BEGIN_FN("IncreasePortBitArraySize");
|
|
|
|
TRC_ASSERT(RDPDRPRT_INITIALPORTCOUNT != 0, (TB, "invalid port count."));
|
|
|
|
newPortBitArraySize = PortBitArraySizeInBytes +
|
|
((RDPDRPRT_INITIALPORTCOUNT / BITSINULONG)
|
|
* sizeof(ULONG));
|
|
if (RDPDRPRT_INITIALPORTCOUNT % BITSINULONG) {
|
|
newPortBitArraySize += sizeof(ULONG);
|
|
}
|
|
|
|
//
|
|
// Allocate the new port bit array.
|
|
//
|
|
newPortBitArray = (ULONG *)new(NonPagedPool) BYTE[newPortBitArraySize];
|
|
if (newPortBitArray != NULL) {
|
|
RtlZeroMemory(newPortBitArray, newPortBitArraySize);
|
|
RtlCopyBytes(newPortBitArray, PortBitArray, PortBitArraySizeInBytes);
|
|
delete PortBitArray;
|
|
PortBitArray = newPortBitArray;
|
|
PortBitArraySizeInBytes = newPortBitArraySize;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
TRC_ERR((TB, "Error allocating %ld bytes for port array",
|
|
newPortBitArraySize));
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
BOOL
|
|
PortIsAvailableForUse(
|
|
IN HANDLE regHandle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Return TRUE if the port's device interface is usable, from dynamon's
|
|
perspective.
|
|
|
|
Arguments:
|
|
|
|
regHandle - Registry handle to port's device interface.
|
|
|
|
Return Value:
|
|
|
|
Return TRUE if the port's device interface is usable.
|
|
|
|
--*/
|
|
{
|
|
UNICODE_STRING unicodeStr;
|
|
NTSTATUS s;
|
|
ULONG bytesReturned;
|
|
BOOL usable;
|
|
BYTE basicValueInformation[sizeof(KEY_VALUE_BASIC_INFORMATION) + 256];
|
|
|
|
BEGIN_FN("PortIsAvailableForUse");
|
|
|
|
//
|
|
// Make sure that the basicValueInformation buffer is large enough
|
|
// to test the existence of the values we are testing for.
|
|
//
|
|
TRC_ASSERT((sizeof(RECYCLABLE_FLAG_VALUE_NAME) < 256) &&
|
|
(sizeof(PORT_NUM_VALUE_NAME) < 256),
|
|
(TB, "Increase basic value buffer."));
|
|
|
|
//
|
|
// If there is no client device path registry value for the port's
|
|
// device interface then the port is brand new and, therefore,
|
|
// usable.
|
|
//
|
|
RtlInitUnicodeString(&unicodeStr, CLIENT_DEVICE_VALUE_NAME);
|
|
s = ZwQueryValueKey(
|
|
regHandle,
|
|
&unicodeStr,
|
|
KeyValueBasicInformation,
|
|
(PVOID)basicValueInformation,
|
|
sizeof(basicValueInformation),
|
|
&bytesReturned
|
|
);
|
|
if (s == STATUS_OBJECT_NAME_NOT_FOUND) {
|
|
usable = TRUE;
|
|
}
|
|
//
|
|
// Otherwise, see if dynamon set the recyclable flag.
|
|
//
|
|
else {
|
|
RtlInitUnicodeString(&unicodeStr,
|
|
RECYCLABLE_FLAG_VALUE_NAME);
|
|
s = ZwQueryValueKey(
|
|
regHandle,
|
|
&unicodeStr,
|
|
KeyValueBasicInformation,
|
|
(PVOID)basicValueInformation,
|
|
sizeof(basicValueInformation),
|
|
&bytesReturned
|
|
);
|
|
usable = (s == STATUS_SUCCESS);
|
|
}
|
|
|
|
if (usable) {
|
|
TRC_NRM((TB, "usable and status %08X.", s));
|
|
}
|
|
else {
|
|
TRC_NRM((TB, "not usable and status %08X.", s));
|
|
}
|
|
return usable;
|
|
}
|
|
|
|
NTSTATUS
|
|
AllocatePrinterPort(
|
|
IN ULONG portArrayIndex,
|
|
OUT PWSTR portName,
|
|
OUT ULONG *portNumber,
|
|
OUT HANDLE *regHandle,
|
|
OUT PUNICODE_STRING symbolicLinkName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocate a port.
|
|
|
|
Arguments:
|
|
|
|
portArrayIndex - Index into the port array
|
|
portName - String that holds the allocated port name. There must be
|
|
room in this buffer for RDPDR_MAXPORTNAMELEN wide characters.
|
|
This includes the terminator.
|
|
regHandle - Registry handle for the device interface associated with the
|
|
port. The calling function should close this handle using
|
|
ZwClose.
|
|
symbolicLinkName - symbolic link name returned by IoRegisterDeviceInterface for
|
|
the port's device interface. The symbolic link name is used
|
|
for a number of IO API's. The caller must free this argument
|
|
when finished via a call to RtlFreeUnicodeString.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS is returned on success.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
GUID *pPrinterGuid;
|
|
UNICODE_STRING unicodeStr;
|
|
ULONG ofs, bit;
|
|
BOOL done;
|
|
BOOL symbolicLinkNameAllocated = FALSE;
|
|
|
|
BEGIN_FN("AllocatePrinterPort");
|
|
|
|
//
|
|
// Find an available port.
|
|
//
|
|
*portNumber = portArrayIndex * BITSINULONG;
|
|
done = FALSE;
|
|
while (!done && (status == STATUS_SUCCESS)) {
|
|
status = FindNextFreePortInPortArray(*portNumber, portNumber);
|
|
|
|
//
|
|
// Generate the port name.
|
|
//
|
|
if (status == STATUS_SUCCESS) {
|
|
if (!GeneratePortName(*portNumber, portName)) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Register a device interface for the port. Note that this function
|
|
// opens an existing interface or creates a new one, depending on whether
|
|
// the port currently exists.
|
|
//
|
|
if (status == STATUS_SUCCESS) {
|
|
pPrinterGuid = (GUID *)&DYNPRINT_GUID;
|
|
RtlInitUnicodeString(&unicodeStr, portName);
|
|
status=IoRegisterDeviceInterface(RDPDYN_PDO, pPrinterGuid, &unicodeStr,
|
|
symbolicLinkName);
|
|
}
|
|
symbolicLinkNameAllocated = (status == STATUS_SUCCESS);
|
|
|
|
//
|
|
// Get the reg key for the device interface.
|
|
//
|
|
if (status == STATUS_SUCCESS) {
|
|
status=IoOpenDeviceInterfaceRegistryKey(symbolicLinkName,
|
|
KEY_ALL_ACCESS, regHandle);
|
|
}
|
|
else {
|
|
TRC_NRM((TB, "IoRegisterDeviceInterface failed with %08X",
|
|
status));
|
|
}
|
|
|
|
//
|
|
// See if the port is available for use, from dynamon's perspective.
|
|
//
|
|
if (status == STATUS_SUCCESS) {
|
|
done = PortIsAvailableForUse(*regHandle);
|
|
}
|
|
else {
|
|
*regHandle = INVALID_HANDLE_VALUE;
|
|
TRC_NRM((TB, "IoOpenDeviceInterfaceRegistryKey failed with %08X",
|
|
status));
|
|
}
|
|
|
|
//
|
|
// If this iteration unsuccessfully produced a port.
|
|
//
|
|
if (!done || (status != STATUS_SUCCESS)) {
|
|
|
|
//
|
|
// Release the symbolic link name if it was allocated.
|
|
//
|
|
|
|
if (symbolicLinkNameAllocated) {
|
|
RtlFreeUnicodeString(symbolicLinkName);
|
|
symbolicLinkNameAllocated = FALSE;
|
|
}
|
|
|
|
//
|
|
// If it's not available, from dynamon's perspective, then we need to
|
|
// set it to free in our list so it can be checked later on. This operation
|
|
// needs to be locked in case the port bit array is being reallocated.
|
|
//
|
|
RDPDRPRT_LOCK();
|
|
ofs = (*portNumber) / BITSINULONG;
|
|
bit = (*portNumber) % BITSINULONG;
|
|
PortBitArray[ofs] &= ~(1<<bit);
|
|
RDPDRPRT_UNLOCK();
|
|
|
|
//
|
|
// Clean up the open registry handle.
|
|
//
|
|
if (*regHandle != INVALID_HANDLE_VALUE) {
|
|
ZwClose(*regHandle);
|
|
*regHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
if ((status != STATUS_SUCCESS) && symbolicLinkNameAllocated) {
|
|
RtlFreeUnicodeString(symbolicLinkName);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
BOOL
|
|
GeneratePortName(
|
|
IN ULONG portNumber,
|
|
OUT PWSTR portName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Generate a port name from a port number.
|
|
|
|
Arguments:
|
|
|
|
portNumber - port number component of port name.
|
|
portName - the generated port name is formed from the RDPDRPRT_BASEPORTNAME
|
|
component and the portNumber component. portName is a string
|
|
that holds the generated port name. There must be room in this
|
|
buffer for RDPDR_MAXPORTNAMELEN characters. This includes
|
|
the terminator.
|
|
Return Value:
|
|
|
|
TRUE if a port name could be successfully generated from the port number.
|
|
Otherwise, FALSE.
|
|
--*/
|
|
{
|
|
ULONG baseLen;
|
|
UNICODE_STRING numericUnc;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
WCHAR numericBuf[RDPDR_PORTNAMEDIGITS+1];
|
|
ULONG toPad;
|
|
ULONG i;
|
|
ULONG digitsInPortNumber;
|
|
BOOL done;
|
|
ULONG tmp;
|
|
|
|
BEGIN_FN("GeneratePortName");
|
|
//
|
|
// Compute the number of digits in the port number.
|
|
//
|
|
for (digitsInPortNumber=1,tmp=portNumber/10; tmp>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<toPad; i++) {
|
|
portName[baseLen+i] = L'0';
|
|
}
|
|
|
|
//
|
|
// Add the rest of the name.
|
|
//
|
|
wcscpy(&portName[baseLen+i], numericBuf);
|
|
}
|
|
else {
|
|
//
|
|
// Add the rest of the name.
|
|
//
|
|
wcscpy(&portName[baseLen], numericBuf);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
GeneratePortDescription(
|
|
IN PCSTR dosPortName,
|
|
IN PCWSTR clientName,
|
|
IN PWSTR description
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Format a port description.
|
|
|
|
Arguments:
|
|
|
|
dosPortName - Preferred DOS name for port.
|
|
clientName - Client name (computer name).
|
|
description - Buffer for formatted port description. This buffer
|
|
must be at least RDPDRPRT_PORTDESCRLENGTH characters
|
|
wide.
|
|
|
|
Return Value:
|
|
|
|
NA
|
|
|
|
--*/
|
|
{
|
|
BEGIN_FN("GeneratePortDescription");
|
|
|
|
swprintf(description, L"%-s: %S", clientName, dosPortName);
|
|
}
|
|
|
|
NTSTATUS
|
|
SetPortDescrToDisabled(
|
|
IN PUNICODE_STRING symbolicLinkName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set the port description string to disabled.
|
|
|
|
Arguments:
|
|
|
|
symbolicLinkName - Symbolic link name.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if successful,
|
|
STATUS_UNSUCCESSFUL otherwise
|
|
--*/
|
|
{
|
|
HANDLE hInterfaceKey = INVALID_HANDLE_VALUE;
|
|
NTSTATUS status;
|
|
UNICODE_STRING unicodeStr;
|
|
|
|
BEGIN_FN("SetPortDescrToDisabled");
|
|
TRC_NRM((TB, "Symbolic link name: %wZ",
|
|
symbolicLinkName));
|
|
|
|
//
|
|
// Get the reg key for our device interface.
|
|
//
|
|
status=IoOpenDeviceInterfaceRegistryKey(
|
|
symbolicLinkName,
|
|
KEY_ALL_ACCESS,&hInterfaceKey
|
|
);
|
|
if (!NT_SUCCESS(status)) {
|
|
hInterfaceKey = INVALID_HANDLE_VALUE;
|
|
TRC_ERR((TB, "IoOpenDeviceInterfaceRegistryKey failed: %08X.",
|
|
status));
|
|
goto CleanUpAndReturn;
|
|
}
|
|
|
|
//
|
|
// Set the string value.
|
|
//
|
|
RtlInitUnicodeString(&unicodeStr, PORT_DESCR_VALUE_NAME);
|
|
status=ZwSetValueKey(hInterfaceKey, &unicodeStr, 0, REG_SZ,
|
|
DisabledPortDescription,
|
|
DisabledPortDescrSize);
|
|
if (!NT_SUCCESS(status)) {
|
|
TRC_ERR((TB, "ZwSetValueKey failed with status %08X.", status));
|
|
goto CleanUpAndReturn;
|
|
}
|
|
|
|
CleanUpAndReturn:
|
|
|
|
if (hInterfaceKey != INVALID_HANDLE_VALUE) {
|
|
ZwClose(hInterfaceKey);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
CleanUpExistingPorts()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Clean up ports registered in a previous boot.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if successful.
|
|
--*/
|
|
{
|
|
NTSTATUS returnStatus;
|
|
NTSTATUS status;
|
|
PWSTR symbolicLinkList=NULL;
|
|
PWSTR symbolicLink;
|
|
ULONG len;
|
|
HANDLE deviceInterfaceKey = INVALID_HANDLE_VALUE;
|
|
UNICODE_STRING unicodeStr;
|
|
ULONG bytesReturned;
|
|
BYTE basicValueInformation[sizeof(KEY_VALUE_BASIC_INFORMATION) + 256];
|
|
|
|
BEGIN_FN("CleanUpExistingPorts");
|
|
|
|
//
|
|
// Make sure that the basicValueInformation buffer is large enough
|
|
// to test the existence of the values we are testing for.
|
|
//
|
|
TRC_ASSERT((sizeof(RECYCLABLE_FLAG_VALUE_NAME) < 256),
|
|
(TB, "Increase basic value buffer size."));
|
|
|
|
//
|
|
// Fetch all the device interfaces for dynamic printer ports created
|
|
// by this driver.
|
|
//
|
|
TRC_ASSERT(RDPDYN_PDO != NULL, (TB, "RDPDYN_PDO == NULL"));
|
|
returnStatus = IoGetDeviceInterfaces(
|
|
&DYNPRINT_GUID,
|
|
RDPDYN_PDO,
|
|
DEVICE_INTERFACE_INCLUDE_NONACTIVE,
|
|
&symbolicLinkList
|
|
);
|
|
|
|
if (returnStatus == STATUS_SUCCESS) {
|
|
//
|
|
// Remove the port number value for each interface to indicate that the
|
|
// port is no longer in use to dynamon.
|
|
//
|
|
symbolicLink = symbolicLinkList;
|
|
len = wcslen(symbolicLink);
|
|
while (len > 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;
|
|
}
|