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.
5690 lines
167 KiB
5690 lines
167 KiB
|
|
/*++
|
|
|
|
Copyright (c) 1998 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
umrdpprn.c
|
|
|
|
Abstract:
|
|
|
|
User-Mode Component for RDP Device Management that Handles Printing Device-
|
|
Specific tasks.
|
|
|
|
This is a supporting module. The main module is umrdpdr.c.
|
|
|
|
Author:
|
|
|
|
TadB
|
|
|
|
Revision History:
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#include <winspool.h>
|
|
#include <rdpdr.h>
|
|
#include <aclapi.h>
|
|
#include "setupapi.h"
|
|
#include "printui.h"
|
|
#include "drdevlst.h"
|
|
#include "umrdpdr.h"
|
|
#include "umrdpprn.h"
|
|
#include "drdbg.h"
|
|
#include "rdpprutl.h"
|
|
#include "tsnutl.h"
|
|
#include "rdpdr.h"
|
|
|
|
#include "errorlog.h"
|
|
#include <wlnotify.h>
|
|
#include <time.h>
|
|
|
|
////////////////////////////////////////////////////////
|
|
//
|
|
// Defines
|
|
//
|
|
|
|
#ifndef BOOL
|
|
#define BOOL int
|
|
#endif
|
|
|
|
#ifndef ARRAYSIZE
|
|
#define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0]))
|
|
#endif
|
|
|
|
#define PRINTUILIBNAME TEXT("printui.dll")
|
|
|
|
//
|
|
// Printui Printer Configuration Save/Restore Flags.
|
|
//
|
|
|
|
// This one should be called as user for fetching the configuration data.
|
|
#define CMDLINE_FOR_STORING_CONFIGINFO_IMPERSONATE L"/q /Ss /n \"%ws\" /a \"%ws\" 2 7 c d u g"
|
|
|
|
// This one should be called first as system for restoring configuration data.
|
|
#define CMDLINE_FOR_RESTORING_CONFIGINFO_NOIMPERSONATE L"/q /Sr /n \"%ws\" /a \"%ws\" 2 7 c d g r p h i"
|
|
// This one should be called second as user for restoring configuration data.
|
|
#define CMDLINE_FOR_RESTORING_CONFIGINFO_IMPERSONATE L"/q /Sr /n \"%ws\" /a \"%ws\" u r"
|
|
|
|
|
|
#define TEMP_FILE_PREFIX L"prn"
|
|
|
|
#define INF_PATH L"\\inf\\ntprint.inf"
|
|
|
|
//
|
|
// Reg key for configurable parms.
|
|
//
|
|
#define CONFIGREGKEY \
|
|
L"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\Wds\\rdpwd"
|
|
|
|
//
|
|
// Location of configurable threshold for delta between printer installation
|
|
// time and forwarding of first user-initiated configuration change data
|
|
// to the client. The units for this value is in seconds.
|
|
//
|
|
#define CONFIGTHRESHOLDREGVALUE \
|
|
L"PrintRdrConfigThreshold"
|
|
|
|
//
|
|
// Default Value for Configurable Printer Configuration Threshold
|
|
//
|
|
#define CONFIGTHRESHOLDDEFAULT 20
|
|
|
|
//
|
|
// Registry key for storing default, per-user, printer names.
|
|
//
|
|
#define USERDEFAULTPRNREGKEY \
|
|
L"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\Wds\\rdpwd\\DefaultPrinterStore"
|
|
|
|
#define TSSERIALDEVICEMAP \
|
|
L"HARDWARE\\DEVICEMAP\\SERIALCOMM"
|
|
|
|
//
|
|
// Registry location of configurable client driver name mapping INF and INF
|
|
// section.
|
|
//
|
|
#define CONFIGUSERDEFINEDMAPPINGINFNAMEVALUE\
|
|
L"PrinterMappingINFName"
|
|
#define CONFIGUSERDEFINEDMAPPINGINFSECTIONVALUE\
|
|
L"PrinterMappingINFSection"
|
|
|
|
//
|
|
// Location of Windows Directory Path
|
|
//
|
|
#define WINDOWSDIRKEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
|
|
#define WINDOWSDIRVALUENAME L"PathName"
|
|
|
|
// Get a numeric representation of our session ID.
|
|
#if defined(UNITTEST)
|
|
#define GETTHESESSIONID() 0
|
|
#else
|
|
extern ULONG g_SessionId;
|
|
#define GETTHESESSIONID() g_SessionId
|
|
#endif
|
|
|
|
#if defined(UNITTEST)
|
|
HINSTANCE g_hInstance;
|
|
#else
|
|
extern HINSTANCE g_hInstance;
|
|
#endif
|
|
|
|
extern BOOL fRunningOnPTS;
|
|
|
|
// Return true if the device type represents a serial or parallel port.
|
|
#define ISPRINTPORT(type) (((type) == RDPDR_DTYP_SERIAL) || \
|
|
((type) == RDPDR_DTYP_PARALLEL) || \
|
|
((type) == RDPDR_DRYP_PRINTPORT))
|
|
|
|
// Maximum number of characters in a session ID (Max chars in a dword is 17)
|
|
#define MAXSESSIONIDCHARS 17
|
|
|
|
// The field types we are waiting on (Printer Config change Notification)
|
|
#define IS_CONFIG_INFO_FIELD(field) \
|
|
(field == PRINTER_NOTIFY_FIELD_SHARE_NAME) || \
|
|
(field == PRINTER_NOTIFY_FIELD_DEVMODE) || \
|
|
(field == PRINTER_NOTIFY_FIELD_COMMENT) || \
|
|
(field == PRINTER_NOTIFY_FIELD_LOCATION) || \
|
|
(field == PRINTER_NOTIFY_FIELD_SEPFILE) || \
|
|
(field == PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR) || \
|
|
(field == PRINTER_NOTIFY_FIELD_PARAMETERS) || \
|
|
(field == PRINTER_NOTIFY_FIELD_DATATYPE) || \
|
|
(field == PRINTER_NOTIFY_FIELD_ATTRIBUTES) || \
|
|
(field == PRINTER_NOTIFY_FIELD_PRIORITY) || \
|
|
(field == PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY) || \
|
|
(field == PRINTER_NOTIFY_FIELD_START_TIME) || \
|
|
(field == PRINTER_NOTIFY_FIELD_UNTIL_TIME) || \
|
|
(field == PRINTER_NOTIFY_FIELD_STATUS)
|
|
|
|
#define CONFIG_WAIT_PERIOD (30 * 1000)
|
|
|
|
#define INFINITE_WAIT_PERIOD (0xDFFFFFFF) //just a large number.Ok for 64-bit also.
|
|
|
|
#define DEVICE_MAP_NAME L"\\??\\"
|
|
#define DEVICE_MAP_NAME_COUNT 4 // 4 chars - \??\
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Typedefs
|
|
//
|
|
typedef struct tagPRINTNOTIFYREC
|
|
{
|
|
HANDLE notificationObject; // Notification object registered
|
|
// FindFirstPrinterChangeNotification.
|
|
HANDLE printerHandle; // Open handle to the printer.
|
|
DWORD serverDeviceID; // Server-side printer identifier.
|
|
} PRINTNOTIFYREC, *PPRINTNOTIFYREC;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// External Prototypes
|
|
//
|
|
#if DBG
|
|
extern void DbgMsg(CHAR *msgFormat, ...);
|
|
#endif
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Local Prototypes
|
|
//
|
|
|
|
WCHAR *ANSIToUnicode(
|
|
IN LPCSTR ansiString,
|
|
IN UINT codePage
|
|
);
|
|
BOOL InstallPrinterWithPortName(
|
|
IN DWORD deviceID,
|
|
IN HANDLE hTokenForLoggedOnUser,
|
|
IN BOOL bSetDefault,
|
|
IN ULONG ulFlags,
|
|
IN PCWSTR portName,
|
|
IN PCWSTR driverName,
|
|
IN PCWSTR printerName,
|
|
IN PCWSTR clientComputerName,
|
|
IN PBYTE cacheData,
|
|
IN DWORD cacheDataLen
|
|
);
|
|
BOOL HandlePrinterNameChangeNotification(
|
|
IN DWORD serverDeviceID,
|
|
IN LPWSTR printerName
|
|
);
|
|
BOOL SendAddPrinterMsgToClient(
|
|
IN PCWSTR printerName,
|
|
IN PCWSTR driverName,
|
|
IN PCSTR dosDevicePort
|
|
);
|
|
BOOL SendDeletePrinterMsgToClient(
|
|
IN PCWSTR printerName
|
|
);
|
|
BOOL HandlePrinterDeleteNotification(
|
|
IN DWORD serverDeviceID
|
|
);
|
|
void HandlePrinterRefreshNotification(
|
|
IN PPRINTER_NOTIFY_INFO notifyInfo
|
|
);
|
|
|
|
DWORD AddSessionIDToPrinterQueue(
|
|
IN HANDLE hPrinter,
|
|
IN DWORD sessionID
|
|
);
|
|
|
|
BOOL SetDefaultPrinterToFirstFound(
|
|
BOOL impersonate
|
|
);
|
|
|
|
DWORD GetPrinterConfigInfo(
|
|
LPCWSTR printerName,
|
|
LPBYTE * ppBuffer,
|
|
LPDWORD pdwBufSize
|
|
);
|
|
DWORD SetPrinterConfigInfo(
|
|
LPCWSTR printerName,
|
|
LPVOID lpBuffer,
|
|
DWORD dwBufSize
|
|
);
|
|
|
|
BOOL HandlePrinterConfigChangeNotification(
|
|
IN DWORD serverDeviceID
|
|
);
|
|
BOOL SendPrinterConfigInfoToClient(
|
|
IN PCWSTR printerName,
|
|
IN LPBYTE pConfigInfo,
|
|
IN DWORD ConfigInfoSize
|
|
);
|
|
|
|
DWORD CallPrintUiPersistFunc(
|
|
LPCWSTR printerName,
|
|
LPCWSTR fileName,
|
|
LPCWSTR formatString
|
|
);
|
|
BOOL
|
|
SendPrinterRenameToClient(
|
|
IN PCWSTR oldprinterName,
|
|
IN PCWSTR newprinterName
|
|
);
|
|
VOID LoadConfigurableValues();
|
|
BOOL GetPrinterPortName(
|
|
IN HANDLE hPrinter,
|
|
OUT PWSTR *portName
|
|
);
|
|
|
|
BOOL MapClientPrintDriverName(
|
|
IN PCWSTR clientDriver,
|
|
IN OUT PWSTR *mappedName,
|
|
IN OUT DWORD *mappedNameBufSize
|
|
);
|
|
|
|
DWORD PrinterDriverInstalled(
|
|
IN PCWSTR clientDriver
|
|
);
|
|
|
|
HANDLE RegisterForPrinterPrefNotify();
|
|
|
|
PACL GiveLoggedOnUserFullPrinterAccess(
|
|
IN LPTSTR printerName,
|
|
IN HANDLE hToken,
|
|
PSECURITY_DESCRIPTOR *ppsd
|
|
);
|
|
|
|
DWORD SetPrinterDACL(
|
|
IN LPTSTR printerName,
|
|
IN PACL pDacl
|
|
);
|
|
|
|
VOID CloseWaitablePrintingObjects();
|
|
|
|
VOID GlobalPrintNotifyObjectSignaled(
|
|
IN HANDLE waitableObject,
|
|
IN PVOID clientData
|
|
);
|
|
VOID PrintPreferenceChangeEventSignaled(
|
|
HANDLE eventHandle,
|
|
PVOID clientData
|
|
);
|
|
void WaitableTimerSignaled(
|
|
HANDLE waitableObject,
|
|
PVOID clientData
|
|
);
|
|
BOOL
|
|
RegisterPrinterConfigChangeNotification(
|
|
IN DWORD serverDeviceID
|
|
);
|
|
BOOL RestoreDefaultPrinterContext();
|
|
BOOL SaveDefaultPrinterContext(PCWSTR currentlyInstallingPrinterName);
|
|
BOOL SavePrinterNameAsGlobalDefault(
|
|
IN PCWSTR printerName
|
|
);
|
|
|
|
// Struct used to split a full printer name.
|
|
// Once filled, each psz points to a substring in the buffer
|
|
// or to one of the original names. Nothing allocated.
|
|
typedef struct _TS_PRINTER_NAMES {
|
|
WCHAR szTemp[MAX_PATH+1];
|
|
ULONG ulTempLen;
|
|
PCWSTR pszFullName;
|
|
PCWSTR pszCurrentClient;
|
|
PCWSTR pszServer;
|
|
PCWSTR pszClient;
|
|
PCWSTR pszPrinter;
|
|
} TS_PRINTER_NAMES, *PTS_PRINTER_NAMES;
|
|
|
|
void FormatPrinterName(
|
|
PWSTR pszNewNameBuf,
|
|
ULONG ulBufLen,
|
|
ULONG ulFlags,
|
|
PTS_PRINTER_NAMES pPrinterNames);
|
|
|
|
DWORD AddNamesToPrinterQueue(
|
|
IN HANDLE hPrinter,
|
|
IN PTS_PRINTER_NAMES pPrinterNames
|
|
);
|
|
|
|
VOID TriggerConfigChangeTimer();
|
|
|
|
#ifdef UNITTEST
|
|
void TellDrToAddTestPrinter();
|
|
void SimpleUnitTest();
|
|
#endif
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
//
|
|
// Globals
|
|
//
|
|
|
|
//
|
|
// Set to TRUE when the DLL is trying to shut down.
|
|
//
|
|
extern BOOL ShutdownFlag;
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
//
|
|
// Globals to this Module
|
|
//
|
|
|
|
// True if this module has been successfully initialized.
|
|
BOOL PrintingModuleInitialized = FALSE;
|
|
|
|
// Comprehensive Device List
|
|
PDRDEVLST DeviceList;
|
|
|
|
// Handle to the print system dev mode for this user. This is the key
|
|
// that is modified when a user changes a printer's printing preferences.
|
|
HKEY DevModeHKey = INVALID_HANDLE_VALUE;
|
|
|
|
// Configurable threshold for delta between printer installation
|
|
// time and forwarding of first user-initiated configuration change data
|
|
// to the client. The units for this value is in seconds.
|
|
DWORD ConfigSendThreshold;
|
|
|
|
//
|
|
// Printer Change Notification Events
|
|
//
|
|
HANDLE PrintNotificationEvent = INVALID_HANDLE_VALUE;
|
|
HANDLE PrintPreferenceChangeEvent = NULL;
|
|
|
|
HANDLE PrintUILibHndl = NULL;
|
|
FARPROC PrintUIEntryFunc = NULL;
|
|
FARPROC PnpInterfaceFunc = NULL;
|
|
HANDLE UMRPDPPRN_TokenForLoggedOnUser = INVALID_HANDLE_VALUE;
|
|
HANDLE LocalPrinterServerHandle = NULL;
|
|
WCHAR PrinterInfPath[MAX_PATH + (sizeof(INF_PATH)/sizeof(WCHAR)) + 2] = L""; // of the form "%windir%\\inf\\ntprint.inf"
|
|
LPPRINTER_INFO_2 PrinterInfo2Buf = NULL;
|
|
DWORD PrinterInfo2BufSize = 0;
|
|
//WCHAR SessionString[MAX_PATH+1];
|
|
WCHAR g_szFromFormat[MAX_PATH+1];
|
|
WCHAR g_szOnFromFormat[MAX_PATH+1];
|
|
|
|
BOOL g_fIsPTS = FALSE;
|
|
|
|
BOOL g_fTimerSet = FALSE;
|
|
HANDLE WaitableTimer = NULL;
|
|
|
|
BOOL g_fDefPrinterEncountered = FALSE;
|
|
|
|
// Global debug flag.
|
|
extern DWORD GLOBAL_DEBUG_FLAGS;
|
|
|
|
// Printer Notify Parameters
|
|
WORD PrinterFieldType[] =
|
|
{
|
|
PRINTER_NOTIFY_FIELD_SHARE_NAME,
|
|
PRINTER_NOTIFY_FIELD_PRINTER_NAME,
|
|
PRINTER_NOTIFY_FIELD_COMMENT,
|
|
PRINTER_NOTIFY_FIELD_LOCATION,
|
|
PRINTER_NOTIFY_FIELD_DEVMODE,
|
|
PRINTER_NOTIFY_FIELD_SEPFILE,
|
|
PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR,
|
|
PRINTER_NOTIFY_FIELD_PARAMETERS,
|
|
PRINTER_NOTIFY_FIELD_DATATYPE,
|
|
PRINTER_NOTIFY_FIELD_ATTRIBUTES,
|
|
PRINTER_NOTIFY_FIELD_PRIORITY,
|
|
PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY,
|
|
PRINTER_NOTIFY_FIELD_START_TIME,
|
|
PRINTER_NOTIFY_FIELD_UNTIL_TIME,
|
|
PRINTER_NOTIFY_FIELD_STATUS
|
|
};
|
|
|
|
PRINTER_NOTIFY_OPTIONS_TYPE PrinterNotifyOptionsType[] =
|
|
{
|
|
{
|
|
PRINTER_NOTIFY_TYPE,
|
|
0,
|
|
0,
|
|
0,
|
|
sizeof(PrinterFieldType) / sizeof(WORD),
|
|
PrinterFieldType
|
|
}
|
|
};
|
|
PRINTER_NOTIFY_OPTIONS PrinterNotifyOptions =
|
|
{
|
|
2,
|
|
0,
|
|
sizeof(PrinterNotifyOptionsType) / sizeof(PRINTER_NOTIFY_OPTIONS_TYPE),
|
|
PrinterNotifyOptionsType
|
|
};
|
|
|
|
//
|
|
// User-Configurable Client Driver Mapping INF Name and INF Section.
|
|
//
|
|
LPWSTR UserDefinedMappingINFName = NULL;
|
|
LPWSTR UserDefinedMappingINFSection = NULL;
|
|
|
|
//
|
|
// Buffer for converting from Win9x driver names to Win2K driver names.
|
|
//
|
|
PWSTR MappedDriverNameBuf = NULL;
|
|
DWORD MappedDriverNameBufSize = 0;
|
|
|
|
//
|
|
// Waitable Object Manager
|
|
//
|
|
WTBLOBJMGR UMRDPPRN_WaitableObjMgr = NULL;
|
|
|
|
//
|
|
// Default Printer Name
|
|
//
|
|
WCHAR SavedDefaultPrinterName[MAX_PATH+1] = L"";
|
|
|
|
//
|
|
// Printer section names
|
|
//
|
|
|
|
static WCHAR* prgwszPrinterSectionNames[] = {
|
|
L"Printer Driver Mapping_Windows NT x86_Version 2",
|
|
L"Printer Driver Mapping_Windows NT x86_Version 3",
|
|
NULL
|
|
};
|
|
|
|
|
|
BOOL IsItPTS()
|
|
{
|
|
OSVERSIONINFOEX gOsVersion;
|
|
ZeroMemory(&gOsVersion, sizeof(OSVERSIONINFOEX));
|
|
gOsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
GetVersionEx( (LPOSVERSIONINFO ) &gOsVersion);
|
|
|
|
return (gOsVersion.wProductType == VER_NT_WORKSTATION) // product type must be workstation.
|
|
&& !(gOsVersion.wSuiteMask & VER_SUITE_PERSONAL) // and product suite must not be personal.
|
|
&& (gOsVersion.wSuiteMask & VER_SUITE_SINGLEUSERTS); // it must be single user ts.
|
|
}
|
|
|
|
|
|
BOOL UMRDPPRN_Initialize(
|
|
IN PDRDEVLST deviceList,
|
|
IN WTBLOBJMGR waitableObjMgr,
|
|
IN HANDLE hTokenForLoggedOnUser
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize this module. This must be called prior to any other functions
|
|
in this module being called.
|
|
|
|
Arguments:
|
|
|
|
deviceList - Comprehensive list of redirected devices.
|
|
waitableObjMgr - Waitable object manager.
|
|
hTokenForLoggedOnUser - This is the token for the logged in user.
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
HKEY regKey;
|
|
LONG sz;
|
|
DWORD errorEventID = -1;
|
|
DWORD errorEventLineNumber = 0;
|
|
|
|
BOOL result, impersonated = FALSE;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:UMRDPPRN_Initialize.\n"));
|
|
|
|
//
|
|
// Make sure we don't get called twice without getting cleaned up.
|
|
//
|
|
ASSERT((PrintNotificationEvent == INVALID_HANDLE_VALUE) &&
|
|
(PrintPreferenceChangeEvent == NULL) &&
|
|
(PrintUILibHndl == NULL) &&
|
|
(PrintUIEntryFunc == NULL) &&
|
|
(PnpInterfaceFunc == NULL) &&
|
|
(UMRPDPPRN_TokenForLoggedOnUser == INVALID_HANDLE_VALUE) &&
|
|
(PrinterInfo2Buf == NULL) &&
|
|
(PrinterInfo2BufSize == 0) &&
|
|
(UMRDPPRN_WaitableObjMgr == NULL) &&
|
|
(WaitableTimer == NULL) &&
|
|
(DeviceList == NULL) &&
|
|
!PrintingModuleInitialized);
|
|
|
|
//
|
|
// Is it PTS?
|
|
//
|
|
g_fIsPTS = IsItPTS();
|
|
|
|
//
|
|
// Zero the default printer name record.
|
|
//
|
|
wcscpy(SavedDefaultPrinterName, L"");
|
|
|
|
//
|
|
// Record the device list.
|
|
//
|
|
DeviceList = deviceList;
|
|
|
|
//
|
|
// Record the waitable object mananager.
|
|
//
|
|
UMRDPPRN_WaitableObjMgr = waitableObjMgr;
|
|
|
|
//
|
|
// Record the token for the logged in user.
|
|
//
|
|
UMRPDPPRN_TokenForLoggedOnUser = hTokenForLoggedOnUser;
|
|
|
|
ASSERT(UMRPDPPRN_TokenForLoggedOnUser != NULL);
|
|
|
|
if (UMRPDPPRN_TokenForLoggedOnUser != NULL) {
|
|
impersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser);
|
|
//
|
|
// not fatal. Just a perf hit
|
|
//
|
|
if (!impersonated) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Impersonation failed. Error: %ld\n", GetLastError()));
|
|
}
|
|
|
|
}
|
|
//
|
|
// Open the local print server while impersonating.
|
|
// We need to impersonate so that notifications for
|
|
// the printers belonging to the current user's sessions
|
|
// are not sent to any other sessions.
|
|
//
|
|
|
|
result = OpenPrinter(NULL, &LocalPrinterServerHandle, NULL);
|
|
|
|
if (impersonated) {
|
|
RevertToSelf();
|
|
}
|
|
|
|
//
|
|
// Initialize the print utility module, RDPDRPRT
|
|
//
|
|
if (result) {
|
|
result = RDPDRUTL_Initialize(hTokenForLoggedOnUser);
|
|
if (!result) {
|
|
errorEventID = EVENT_NOTIFY_INSUFFICIENTRESOURCES;
|
|
errorEventLineNumber = __LINE__;
|
|
}
|
|
}
|
|
else {
|
|
errorEventID = EVENT_NOTIFY_SPOOLERERROR;
|
|
errorEventLineNumber = __LINE__;
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error opening printer. Error: %ld\n",
|
|
GetLastError()));
|
|
}
|
|
|
|
//
|
|
// Load configurable values out of the registry.
|
|
//
|
|
LoadConfigurableValues();
|
|
|
|
//
|
|
// Create the timer even that we use for staggering printer
|
|
// configuration changes to the client.
|
|
//
|
|
if (result) {
|
|
WaitableTimer = CreateWaitableTimer(
|
|
NULL, // Security Attribs
|
|
TRUE, // Manual Reset
|
|
NULL); // Timer Name
|
|
if (WaitableTimer != NULL) {
|
|
if (WTBLOBJ_AddWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr, NULL,
|
|
WaitableTimer,
|
|
WaitableTimerSignaled
|
|
) != ERROR_SUCCESS) {
|
|
errorEventID = EVENT_NOTIFY_INSUFFICIENTRESOURCES;
|
|
errorEventLineNumber = __LINE__;
|
|
result = FALSE;
|
|
}
|
|
|
|
}
|
|
else {
|
|
errorEventID = EVENT_NOTIFY_INSUFFICIENTRESOURCES;
|
|
errorEventLineNumber = __LINE__;
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error creating Waitable timer. Error: %ld\n",
|
|
GetLastError()));
|
|
result = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Register for changes to one of this session's printers' Printing
|
|
// Preferences.
|
|
//
|
|
if (result) {
|
|
PrintPreferenceChangeEvent = RegisterForPrinterPrefNotify();
|
|
if (PrintPreferenceChangeEvent != NULL) {
|
|
if (WTBLOBJ_AddWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr, NULL,
|
|
PrintPreferenceChangeEvent,
|
|
PrintPreferenceChangeEventSignaled
|
|
) != ERROR_SUCCESS) {
|
|
errorEventID = EVENT_NOTIFY_INSUFFICIENTRESOURCES;
|
|
errorEventLineNumber = __LINE__;
|
|
result = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
result = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Register for change notification on addition/deletion of a
|
|
// printer.
|
|
//
|
|
if (result) {
|
|
PrintNotificationEvent = FindFirstPrinterChangeNotification(
|
|
LocalPrinterServerHandle,
|
|
0, 0, &PrinterNotifyOptions
|
|
);
|
|
if (PrintNotificationEvent != INVALID_HANDLE_VALUE) {
|
|
DWORD dwLastError = WTBLOBJ_AddWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr, NULL,
|
|
PrintNotificationEvent,
|
|
GlobalPrintNotifyObjectSignaled
|
|
);
|
|
result = dwLastError == ERROR_SUCCESS;
|
|
}
|
|
else {
|
|
DWORD dwLastError = GetLastError();
|
|
|
|
errorEventID = EVENT_NOTIFY_SPOOLERERROR;
|
|
errorEventLineNumber = __LINE__;
|
|
|
|
ClosePrinter(LocalPrinterServerHandle);
|
|
LocalPrinterServerHandle = NULL;
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error registering for printer change notification. Error: %ld\n",
|
|
dwLastError));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Construct the inf path for printer installation
|
|
//
|
|
if (result) {
|
|
DWORD dwResult;
|
|
dwResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WINDOWSDIRKEY, 0,
|
|
KEY_READ, ®Key);
|
|
result = (dwResult == ERROR_SUCCESS);
|
|
if (result) {
|
|
sz = sizeof(PrinterInfPath);
|
|
dwResult = RegQueryValueEx(regKey,
|
|
WINDOWSDIRVALUENAME,
|
|
NULL,
|
|
NULL,
|
|
(PBYTE)PrinterInfPath,
|
|
&sz);
|
|
|
|
result = (dwResult == ERROR_SUCCESS);
|
|
if (result) {
|
|
wcscat(PrinterInfPath, INF_PATH);
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error reading registry value for windows directory. Error: %ld\n",
|
|
dwResult));
|
|
}
|
|
RegCloseKey(regKey);
|
|
}
|
|
else {
|
|
errorEventID = EVENT_NOTIFY_INTERNALERROR;
|
|
errorEventLineNumber = __LINE__;
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error opening registry key for windows directory. Error: %ld\n",
|
|
dwResult));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Load the PrintUILib DLL.
|
|
//
|
|
if (result) {
|
|
PrintUILibHndl = LoadLibrary(PRINTUILIBNAME);
|
|
|
|
result = (PrintUILibHndl != NULL);
|
|
if (!result) {
|
|
errorEventID = EVENT_NOTIFY_INTERNALERROR;
|
|
errorEventLineNumber = __LINE__;
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Unable to load PRINTUI DLL. Error: %ld\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get a pointer to the only entry point that we use.
|
|
//
|
|
if (result) {
|
|
PrintUIEntryFunc = GetProcAddress(PrintUILibHndl, "PrintUIEntryW");
|
|
PnpInterfaceFunc = GetProcAddress(PrintUILibHndl, "PnPInterface");
|
|
result = (PrintUIEntryFunc != NULL && PnpInterfaceFunc != NULL);
|
|
if (!result) {
|
|
errorEventID = EVENT_NOTIFY_INTERNALERROR;
|
|
errorEventLineNumber = __LINE__;
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Unable to locate PRINTUI DLL function. Error: %ld\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialize the printer driver name mapping buffer to a reasonable size.
|
|
// Failure of this function to allocate memory is not a critical error because
|
|
// we can try again later, if necessary.
|
|
//
|
|
UMRDPDR_ResizeBuffer(&MappedDriverNameBuf, MAX_PATH * sizeof(WCHAR),
|
|
&MappedDriverNameBufSize);
|
|
|
|
//
|
|
// Load our localizable "session" printer name component from the resource file.
|
|
//
|
|
if (result) {
|
|
if (!LoadString(g_hInstance,
|
|
g_fIsPTS?IDS_TSPTEMPLATE_FROM:IDS_TSPTEMPLATE_FROM_IN,
|
|
g_szFromFormat,
|
|
sizeof(g_szFromFormat) / sizeof(g_szFromFormat[0])
|
|
)) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:LoadString failed with Error: %ld.\n", GetLastError()));
|
|
g_szFromFormat[0] = L'\0';
|
|
}
|
|
|
|
if (!LoadString(g_hInstance,
|
|
g_fIsPTS?IDS_TSPTEMPLATE_ON_FROM:IDS_TSPTEMPLATE_ON_FROM_IN,
|
|
g_szOnFromFormat,
|
|
sizeof(g_szOnFromFormat) / sizeof(g_szOnFromFormat[0])
|
|
)) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:LoadString failed with Error: %ld.\n", GetLastError()));
|
|
g_szOnFromFormat[0] = L'\0';
|
|
}
|
|
}
|
|
|
|
if (result) {
|
|
DBGMSG(DBG_INFO, ("UMRDPPRN:UMRDPPRN_Initialize succeeded.\n"));
|
|
PrintingModuleInitialized = TRUE;
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Log an error event if we were able to discern one.
|
|
//
|
|
if (errorEventID != -1) {
|
|
TsLogError(errorEventID, EVENTLOG_ERROR_TYPE, 0, NULL, errorEventLineNumber);
|
|
}
|
|
|
|
if (PrintUILibHndl != NULL) {
|
|
FreeLibrary(PrintUILibHndl);
|
|
PrintUILibHndl = NULL;
|
|
}
|
|
PrintUIEntryFunc = NULL;
|
|
PnpInterfaceFunc = NULL;
|
|
|
|
//
|
|
// Close down waitable objects.
|
|
//
|
|
CloseWaitablePrintingObjects();
|
|
|
|
//
|
|
// Zero the waitable object manager.
|
|
//
|
|
UMRDPPRN_WaitableObjMgr = NULL;
|
|
|
|
if (LocalPrinterServerHandle != NULL) {
|
|
ClosePrinter(LocalPrinterServerHandle);
|
|
LocalPrinterServerHandle = NULL;
|
|
}
|
|
|
|
//
|
|
// Release the user-configurable client driver name mapping INF
|
|
// and section names.
|
|
//
|
|
if (UserDefinedMappingINFName != NULL) {
|
|
FREEMEM(UserDefinedMappingINFName);
|
|
UserDefinedMappingINFName = NULL;
|
|
}
|
|
if (UserDefinedMappingINFSection != NULL) {
|
|
FREEMEM(UserDefinedMappingINFSection);
|
|
UserDefinedMappingINFSection = NULL;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
UMRDPPRN_Shutdown()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Close down this module. Right now, we just need to shut down the
|
|
background thread.
|
|
|
|
Arguments:
|
|
|
|
NA
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:UMRDPPRN_Shutdown.\n"));
|
|
|
|
//
|
|
// Check if we are already shutdown
|
|
//
|
|
if (!PrintingModuleInitialized) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Unload printui.dll.
|
|
//
|
|
if (PrintUILibHndl != NULL) {
|
|
FreeLibrary(PrintUILibHndl);
|
|
PrintUILibHndl = NULL;
|
|
}
|
|
|
|
//
|
|
// Zero the printui entry point function.
|
|
//
|
|
PrintUIEntryFunc = NULL;
|
|
PnpInterfaceFunc = NULL;
|
|
|
|
//
|
|
// Shut down the print utility module, RDPDRPRT
|
|
//
|
|
RDPDRUTL_Shutdown();
|
|
|
|
//
|
|
// Close down waitable objects.
|
|
//
|
|
CloseWaitablePrintingObjects();
|
|
|
|
//
|
|
// Zero the waitable object manager.
|
|
//
|
|
UMRDPPRN_WaitableObjMgr = NULL;
|
|
|
|
//
|
|
// Zero the device list.
|
|
//
|
|
DeviceList = NULL;
|
|
|
|
//
|
|
// Close the handle to the open printing system dev mode registry
|
|
// key for this user.
|
|
//
|
|
if (DevModeHKey != INVALID_HANDLE_VALUE) {
|
|
RegCloseKey(DevModeHKey);
|
|
DevModeHKey = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
//
|
|
// Close the handle to the local print server.
|
|
//
|
|
if (LocalPrinterServerHandle != NULL) {
|
|
ClosePrinter(LocalPrinterServerHandle);
|
|
LocalPrinterServerHandle = NULL;
|
|
}
|
|
|
|
//
|
|
// Release the printer information level 2 buffer.
|
|
//
|
|
if (PrinterInfo2Buf != NULL) {
|
|
FREEMEM(PrinterInfo2Buf);
|
|
PrinterInfo2Buf = NULL;
|
|
PrinterInfo2BufSize = 0;
|
|
}
|
|
|
|
//
|
|
// Release the printer driver name conversion buffer.
|
|
//
|
|
if (MappedDriverNameBuf != NULL) {
|
|
FREEMEM(MappedDriverNameBuf);
|
|
MappedDriverNameBuf = NULL;
|
|
MappedDriverNameBufSize = 0;
|
|
}
|
|
|
|
//
|
|
// Release the user-configurable client driver name mapping INF
|
|
// and section names.
|
|
//
|
|
if (UserDefinedMappingINFName != NULL) {
|
|
FREEMEM(UserDefinedMappingINFName);
|
|
UserDefinedMappingINFName = NULL;
|
|
}
|
|
if (UserDefinedMappingINFSection != NULL) {
|
|
FREEMEM(UserDefinedMappingINFSection);
|
|
UserDefinedMappingINFSection = NULL;
|
|
}
|
|
|
|
//
|
|
// Zero the logged on user token.
|
|
//
|
|
UMRPDPPRN_TokenForLoggedOnUser = INVALID_HANDLE_VALUE;
|
|
|
|
//
|
|
// We are no longer initialized.
|
|
//
|
|
PrintingModuleInitialized = FALSE;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:UMRDPPRN_Shutdown succeeded.\n"));
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
CloseWaitablePrintingObjects()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Close out all waitable objects for this module.
|
|
|
|
Arguments:
|
|
|
|
NA
|
|
|
|
Return Value:
|
|
|
|
NA
|
|
|
|
--*/
|
|
{
|
|
DWORD ofs;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:CloseWaitablePrintingObjects begin.\n"));
|
|
|
|
//
|
|
// Scan the device list, looking for printing devices with registered
|
|
// change notifications.
|
|
//
|
|
if (DeviceList != NULL) {
|
|
for (ofs=0; ofs<DeviceList->deviceCount; ofs++) {
|
|
|
|
if ((DeviceList->devices[ofs].deviceType == RDPDR_DTYP_PRINT) &&
|
|
(DeviceList->devices[ofs].deviceSpecificData != NULL)) {
|
|
|
|
PPRINTNOTIFYREC notifyRec = (PPRINTNOTIFYREC)
|
|
DeviceList->devices[ofs].deviceSpecificData;
|
|
ASSERT(notifyRec->notificationObject != NULL);
|
|
ASSERT(notifyRec->printerHandle != NULL);
|
|
|
|
if (UMRDPPRN_WaitableObjMgr != NULL) {
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
notifyRec->notificationObject
|
|
);
|
|
}
|
|
|
|
FindClosePrinterChangeNotification(
|
|
notifyRec->notificationObject
|
|
);
|
|
FREEMEM(notifyRec);
|
|
DeviceList->devices[ofs].deviceSpecificData = NULL;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Close the waitable timer.
|
|
//
|
|
if (WaitableTimer != NULL) {
|
|
if (UMRDPPRN_WaitableObjMgr != NULL) {
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
WaitableTimer
|
|
);
|
|
}
|
|
CloseHandle(WaitableTimer);
|
|
WaitableTimer = NULL;
|
|
}
|
|
|
|
//
|
|
// Close the handle to the printer notification event.
|
|
//
|
|
if (PrintNotificationEvent != INVALID_HANDLE_VALUE) {
|
|
if (UMRDPPRN_WaitableObjMgr != NULL) {
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
PrintNotificationEvent
|
|
);
|
|
}
|
|
FindClosePrinterChangeNotification(PrintNotificationEvent);
|
|
PrintNotificationEvent = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
//
|
|
// Close the handle to the printer preference change notification event.
|
|
//
|
|
if (PrintPreferenceChangeEvent != NULL) {
|
|
if (UMRDPPRN_WaitableObjMgr != NULL) {
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
PrintPreferenceChangeEvent
|
|
);
|
|
}
|
|
CloseHandle(PrintPreferenceChangeEvent);
|
|
PrintPreferenceChangeEvent = NULL;
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:CloseWaitablePrintingObjects end.\n"));
|
|
}
|
|
|
|
BOOL
|
|
UMRDPPRN_HandlePrinterAnnounceEvent(
|
|
IN PRDPDR_PRINTERDEVICE_SUB pPrintAnnounce
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle a printing device announce event from the "dr" by installing a
|
|
local print queue and adding a record for the device to the list of
|
|
installed devices.
|
|
|
|
Arguments:
|
|
|
|
hTokenForLoggedOnUser - Logged on user token.
|
|
pPrintAnnounce - Printer device announce event.
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
PWSTR driverName;
|
|
PWSTR printerName;
|
|
PBYTE pDataFollowingEvent;
|
|
UINT codePage;
|
|
int numChars;
|
|
PWSTR drvNameStringConvertBuf=NULL;
|
|
PWSTR prnNameStringConvertBuf=NULL;
|
|
BOOL result = FALSE;
|
|
PBYTE pPrinterCacheData=NULL;
|
|
DWORD PrinterCacheDataLen=0;
|
|
BOOL bSetDefault = FALSE;
|
|
|
|
#if DBG
|
|
HANDLE hPrinter = NULL;
|
|
PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ALL_ACCESS};
|
|
#endif
|
|
|
|
if (!PrintingModuleInitialized) {
|
|
return FALSE;
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterAnnounceEvent.\n"));
|
|
|
|
// Sanity check the incoming event.
|
|
ASSERT(pPrintAnnounce->deviceFields.DeviceType == RDPDR_DTYP_PRINT);
|
|
ASSERT(pPrintAnnounce->deviceFields.DeviceDataLength >=
|
|
sizeof(PRDPDR_PRINTERDEVICE_ANNOUNCE));
|
|
|
|
// Get a pointer to the data that follows the event.
|
|
pDataFollowingEvent = ((PBYTE)pPrintAnnounce) +
|
|
sizeof(RDPDR_PRINTERDEVICE_SUB);
|
|
|
|
// The driver name is the second field.
|
|
driverName = (PWSTR)(pDataFollowingEvent +
|
|
pPrintAnnounce->clientPrinterFields.PnPNameLen
|
|
);
|
|
|
|
// The printer name is the third field.
|
|
printerName = (PWSTR)(pDataFollowingEvent +
|
|
pPrintAnnounce->clientPrinterFields.PnPNameLen +
|
|
pPrintAnnounce->clientPrinterFields.DriverLen
|
|
);
|
|
// NULL-terminate the names.
|
|
// Length (in bytes) from client includes the null.
|
|
// So, we need to subtract 1 and then NULL-terminate.
|
|
if (pPrintAnnounce->clientPrinterFields.DriverLen > 0) {
|
|
driverName[pPrintAnnounce->clientPrinterFields.DriverLen/sizeof(WCHAR) - 1] = L'\0';
|
|
}
|
|
|
|
if (pPrintAnnounce->clientPrinterFields.PrinterNameLen > 0) {
|
|
printerName[pPrintAnnounce->clientPrinterFields.PrinterNameLen/sizeof(WCHAR) - 1] = L'\0';
|
|
}
|
|
|
|
// Cache data is the last field
|
|
if (pPrintAnnounce->clientPrinterFields.CachedFieldsLen > 0) {
|
|
PrinterCacheDataLen = pPrintAnnounce->clientPrinterFields.CachedFieldsLen;
|
|
pPrinterCacheData = (PBYTE)(pDataFollowingEvent +
|
|
pPrintAnnounce->clientPrinterFields.PnPNameLen +
|
|
pPrintAnnounce->clientPrinterFields.DriverLen +
|
|
pPrintAnnounce->clientPrinterFields.PrinterNameLen
|
|
);
|
|
DBGMSG(DBG_TRACE, ("PrinterNameLen - %ld\n", pPrintAnnounce->clientPrinterFields.PrinterNameLen));
|
|
}
|
|
|
|
//
|
|
// See if we need to convert the name from ANSI to UNICODE.
|
|
//
|
|
if (pPrintAnnounce->clientPrinterFields.Flags & RDPDR_PRINTER_ANNOUNCE_FLAG_ANSI) {
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterAnnounceEvent ansi flag is set.\n"));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterAnnounceEvent converting to unicode.\n"));
|
|
|
|
//
|
|
// Convert the driver name.
|
|
//
|
|
drvNameStringConvertBuf = ANSIToUnicode(
|
|
(LPCSTR)driverName,
|
|
pPrintAnnounce->clientPrinterFields.CodePage
|
|
);
|
|
if (drvNameStringConvertBuf != NULL) {
|
|
driverName = drvNameStringConvertBuf;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Convert the printer name.
|
|
//
|
|
prnNameStringConvertBuf = ANSIToUnicode(
|
|
(LPCSTR)printerName,
|
|
pPrintAnnounce->clientPrinterFields.CodePage
|
|
);
|
|
if (prnNameStringConvertBuf != NULL) {
|
|
printerName = prnNameStringConvertBuf;
|
|
}
|
|
else {
|
|
FREEMEM(drvNameStringConvertBuf);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (pPrintAnnounce->clientPrinterFields.Flags &
|
|
RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER) {
|
|
bSetDefault = TRUE;
|
|
g_fDefPrinterEncountered = TRUE;
|
|
}
|
|
else {
|
|
bSetDefault = (!g_fDefPrinterEncountered) ? TRUE : FALSE;
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterAnnounceEvent driver name is %ws.\n",
|
|
driverName));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterAnnounceEvent printer name is %ws.\n",
|
|
printerName));
|
|
|
|
// We will install the printer using the driver name only for now.
|
|
// Later, we can take advantage of the rest of the fields.
|
|
if (UMRDPDR_fAutoInstallPrinters()) {
|
|
|
|
result = InstallPrinterWithPortName(
|
|
pPrintAnnounce->deviceFields.DeviceId,
|
|
UMRPDPPRN_TokenForLoggedOnUser,
|
|
bSetDefault,
|
|
pPrintAnnounce->clientPrinterFields.Flags,
|
|
pPrintAnnounce->portName,
|
|
driverName,
|
|
printerName,
|
|
pPrintAnnounce->clientName,
|
|
pPrinterCacheData,
|
|
PrinterCacheDataLen
|
|
);
|
|
}
|
|
else {
|
|
result = TRUE;
|
|
}
|
|
|
|
|
|
// Release any buffers allocated for string conversion.
|
|
if (drvNameStringConvertBuf != NULL) {
|
|
FREEMEM(drvNameStringConvertBuf);
|
|
}
|
|
|
|
if (prnNameStringConvertBuf != NULL) {
|
|
FREEMEM(prnNameStringConvertBuf);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
PrintPreferenceChangeEventSignaled(
|
|
HANDLE eventHandle,
|
|
PVOID clientData
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function handles when the user changes one of this session's printers'
|
|
Printing Preferences settings.
|
|
|
|
Arguments:
|
|
|
|
eventHandle - Signaled event.
|
|
clientData - Client data associated with callback registration.
|
|
|
|
Return Value:
|
|
|
|
NA
|
|
|
|
--*/
|
|
|
|
{
|
|
time_t timeDelta;
|
|
ULONG ofs;
|
|
ULONG ret;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:PrintPreferenceChangeEventSignaled entered.\n"));
|
|
|
|
//
|
|
// Reregister the change notification.
|
|
//
|
|
ASSERT(DevModeHKey != INVALID_HANDLE_VALUE);
|
|
ret = RegNotifyChangeKeyValue(
|
|
DevModeHKey,
|
|
TRUE,
|
|
REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
|
|
eventHandle,
|
|
TRUE
|
|
);
|
|
|
|
//
|
|
// On failure, remove the notification registration.
|
|
//
|
|
if (ret != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Catch this with an assert so we can know how often this happens.
|
|
//
|
|
ASSERT(FALSE);
|
|
|
|
if (PrintPreferenceChangeEvent != NULL) {
|
|
if (UMRDPPRN_WaitableObjMgr != NULL) {
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
PrintPreferenceChangeEvent
|
|
);
|
|
}
|
|
CloseHandle(PrintPreferenceChangeEvent);
|
|
PrintPreferenceChangeEvent = NULL;
|
|
}
|
|
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN: can't register for registry key change event: %ld.\n",
|
|
ret));
|
|
}
|
|
|
|
//
|
|
// Since we have no way of knowing which printer changed, we need to
|
|
// handle this change for all printing devices.
|
|
//
|
|
for (ofs=0; ofs<DeviceList->deviceCount; ofs++) {
|
|
|
|
if (DeviceList->devices[ofs].deviceType == RDPDR_DTYP_PRINT) {
|
|
|
|
//
|
|
// Get the delta between the current time and when this device was
|
|
// installed. It outside the configurable threshhold, then the
|
|
// updated configuration should be sent to the client.
|
|
//
|
|
timeDelta = time(NULL) - DeviceList->devices[ofs].installTime;
|
|
if ((DWORD)timeDelta > ConfigSendThreshold) {
|
|
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:Processing config change because outside time delta.\n")
|
|
);
|
|
|
|
//
|
|
// Need to record that the configuration has changed and set a
|
|
// timer on forwarding to the client in order to compress changes into
|
|
// a single message to the client.
|
|
//
|
|
DeviceList->devices[ofs].fConfigInfoChanged = TRUE;
|
|
TriggerConfigChangeTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:PrintPreferenceChangeEventSignaled done.\n"));
|
|
}
|
|
|
|
void
|
|
GlobalPrintNotifyObjectSignaled(
|
|
HANDLE waitableObject,
|
|
PVOID clientData
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called when the notification object for the local
|
|
print server is signaled. This is how we catch "global" changes to
|
|
the server printer configuration.
|
|
|
|
Changes detected here allow us to detect manually added TS printers
|
|
as well as a subset of possible configuration changes to existing
|
|
printers for this session.
|
|
|
|
Arguments:
|
|
|
|
waitableObject - Associated waitable object.
|
|
clientData - Client data associated with callback registration.
|
|
|
|
Return Value:
|
|
|
|
NA
|
|
|
|
--*/
|
|
{
|
|
DWORD changeValue;
|
|
PPRINTER_NOTIFY_INFO notifyInfo=NULL;
|
|
UINT32 i;
|
|
BOOL result;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:GlobalPrintNotifyObjectSignaled entered.\n"));
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// These two objects should be on in the same.
|
|
//
|
|
ASSERT(PrintNotificationEvent == waitableObject);
|
|
|
|
//
|
|
// Find out what changed.
|
|
//
|
|
PrinterNotifyOptions.Flags &= ~PRINTER_NOTIFY_OPTIONS_REFRESH;
|
|
result = FindNextPrinterChangeNotification(
|
|
PrintNotificationEvent, &changeValue,
|
|
&PrinterNotifyOptions, ¬ifyInfo
|
|
);
|
|
|
|
if (result && (notifyInfo != NULL)) {
|
|
|
|
//
|
|
// If this is not a refresh, then just handle individual notification
|
|
// events.
|
|
//
|
|
if (!(notifyInfo->Flags & PRINTER_NOTIFY_INFO_DISCARDED)) {
|
|
|
|
for (i=0; i<notifyInfo->Count; i++) {
|
|
|
|
// Notification Type must be PRINTER_NOTIFY_TYPE.
|
|
ASSERT(notifyInfo->aData[i].Type == PRINTER_NOTIFY_TYPE);
|
|
|
|
//
|
|
// If we have a printer name change event. This is what we use to
|
|
// detect new printers and renamed printers.
|
|
//
|
|
if (notifyInfo->aData[i].Field == PRINTER_NOTIFY_FIELD_PRINTER_NAME) {
|
|
|
|
HandlePrinterNameChangeNotification(
|
|
notifyInfo->aData[i].Id,
|
|
(LPWSTR)notifyInfo->aData[i].NotifyData.Data.pBuf
|
|
);
|
|
}
|
|
//
|
|
// If the Configuration Information changed.
|
|
//
|
|
else if (IS_CONFIG_INFO_FIELD(notifyInfo->aData[i].Field)) {
|
|
|
|
HandlePrinterConfigChangeNotification(
|
|
notifyInfo->aData[i].Id
|
|
);
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Otherwise, we need to refresh. This is an unusual case.
|
|
//
|
|
else {
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:!!!!FindNextPrinterChangeNotification refresh required.!!!!\n"));
|
|
|
|
//
|
|
// This refreshes the complete list of printers.
|
|
//
|
|
FreePrinterNotifyInfo(notifyInfo);
|
|
notifyInfo = NULL;
|
|
PrinterNotifyOptions.Flags |= PRINTER_NOTIFY_OPTIONS_REFRESH;
|
|
result = FindNextPrinterChangeNotification(
|
|
PrintNotificationEvent, &changeValue,
|
|
&PrinterNotifyOptions, ¬ifyInfo
|
|
);
|
|
|
|
//
|
|
// Make sure our view of the list of available printers
|
|
// is accurate.
|
|
//
|
|
if (result) {
|
|
HandlePrinterRefreshNotification(
|
|
notifyInfo
|
|
);
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:FindNextPrinterChangeNotification failed: %ld.\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// On failure, we need to remove the printer change notification object so we don't
|
|
// get into an infinite loop caused by the notification object never entering a
|
|
// non-signaled state. This can happen on a stressed machine and is an unusual
|
|
// case.
|
|
//
|
|
if (!result) {
|
|
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:FindNextPrinterChangeNotification failed: %ld.\n",
|
|
GetLastError()));
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:Disabling print change notification.\n"));
|
|
|
|
if (PrintNotificationEvent != INVALID_HANDLE_VALUE) {
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
PrintNotificationEvent
|
|
);
|
|
FindClosePrinterChangeNotification(PrintNotificationEvent);
|
|
PrintNotificationEvent = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Release the notification buffer.
|
|
//
|
|
if (notifyInfo != NULL) {
|
|
FreePrinterNotifyInfo(notifyInfo);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
SinglePrinterNotifyObjectSignaled(
|
|
HANDLE waitableObject,
|
|
PPRINTNOTIFYREC notifyRec
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is called when the notification object for a single
|
|
printer is signaled. This function indicates that we need to forward
|
|
configuration information for a specific printer to the client for
|
|
persistent storage.
|
|
|
|
Arguments:
|
|
|
|
waitableObject - Associated waitable object.
|
|
serverDeviceID - Device list identifier for printing device being
|
|
signaled.
|
|
|
|
Return Value:
|
|
|
|
NA
|
|
|
|
--*/
|
|
{
|
|
DWORD ofs;
|
|
BOOL result;
|
|
DWORD changeValue;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SinglePrinterNotifyObjectSignaled entered.\n"));
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
return;
|
|
}
|
|
|
|
result = DRDEVLST_FindByServerDeviceID(
|
|
DeviceList,
|
|
notifyRec->serverDeviceID,
|
|
&ofs);
|
|
ASSERT(result);
|
|
|
|
//
|
|
// Re-register the change notification.
|
|
//
|
|
if (result) {
|
|
|
|
ASSERT(notifyRec ==
|
|
(PPRINTNOTIFYREC)DeviceList->devices[ofs].deviceSpecificData)
|
|
ASSERT(notifyRec->notificationObject != NULL);
|
|
ASSERT(notifyRec->printerHandle != NULL);
|
|
ASSERT(notifyRec->notificationObject == waitableObject);
|
|
|
|
result = FindNextPrinterChangeNotification(
|
|
notifyRec->notificationObject,
|
|
&changeValue,
|
|
NULL, NULL
|
|
);
|
|
}
|
|
|
|
//
|
|
// If this failed, we need to release the change notification
|
|
// object to prevent infinitely looping on a signaled object.
|
|
//
|
|
if (!result) {
|
|
//
|
|
// Catch this with an assert so we can know how often this happens.
|
|
//
|
|
ASSERT(FALSE);
|
|
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
notifyRec->notificationObject
|
|
);
|
|
|
|
FindClosePrinterChangeNotification(
|
|
notifyRec->notificationObject
|
|
);
|
|
ClosePrinter(notifyRec->printerHandle);
|
|
FREEMEM(notifyRec);
|
|
DeviceList->devices[ofs].deviceSpecificData = NULL;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Handle the change.
|
|
//
|
|
if (result) {
|
|
|
|
//
|
|
// If it's a printer deletion.
|
|
//
|
|
if (changeValue & PRINTER_CHANGE_DELETE_PRINTER) {
|
|
|
|
HandlePrinterDeleteNotification(notifyRec->serverDeviceID);
|
|
}
|
|
//
|
|
// If it's a configuration change.
|
|
//
|
|
else if (changeValue &
|
|
(PRINTER_CHANGE_ADD_PRINTER_DRIVER |
|
|
PRINTER_CHANGE_SET_PRINTER_DRIVER |
|
|
PRINTER_CHANGE_DELETE_PRINTER_DRIVER)) {
|
|
|
|
HandlePrinterConfigChangeNotification(notifyRec->serverDeviceID);
|
|
}
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:SinglePrinterNotifyObjectSignaled exit.\n")
|
|
);
|
|
}
|
|
|
|
void
|
|
HandlePrinterRefreshNotification(
|
|
IN PPRINTER_NOTIFY_INFO notifyInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle a print notification refresh from the spooler.
|
|
|
|
Arguments:
|
|
|
|
notifyInfo - Notify info pointer returned by
|
|
FindNextPrinterChangeNotification.
|
|
|
|
Return Value:
|
|
|
|
NA
|
|
|
|
--*/
|
|
{
|
|
DWORD deviceListOfs;
|
|
DWORD notifyOfs;
|
|
LPWSTR printerName;
|
|
DWORD i;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterRefreshNotification entered.\n"));
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Handle printer additions, renames, etc.
|
|
//
|
|
for (i=0; i<notifyInfo->Count; i++) {
|
|
|
|
// Notification Type must be PRINTER_NOTIFY_TYPE.
|
|
ASSERT(notifyInfo->aData[i].Type == PRINTER_NOTIFY_TYPE);
|
|
|
|
//
|
|
// If we have a printer name change event. This is what we use to
|
|
// detect new printers and renamed printers.
|
|
//
|
|
if (notifyInfo->aData[i].Field == PRINTER_NOTIFY_FIELD_PRINTER_NAME) {
|
|
|
|
printerName = (LPWSTR)notifyInfo->aData[i].NotifyData.Data.pBuf;
|
|
HandlePrinterNameChangeNotification(
|
|
notifyInfo->aData[i].Id,
|
|
printerName
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
HandlePrinterDeleteNotification(
|
|
IN DWORD serverDeviceID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle notification from the spooler that a printer has been deleted.hanged.
|
|
|
|
Arguments:
|
|
|
|
serverDeviceID - Server-assigned device ID for printer being deleted.
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
DWORD ofs;
|
|
BOOL result;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterDeleteNotification with server ID %ld.\n",
|
|
serverDeviceID));
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If this is for one of our printers.
|
|
//
|
|
if (DRDEVLST_FindByServerDeviceID(DeviceList,
|
|
serverDeviceID, &ofs)) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:****Printer %ws has been removed.****\n",
|
|
DeviceList->devices[ofs].serverDeviceName));
|
|
|
|
//
|
|
// Send a message to the client to let it know that a printer has been
|
|
// deleted.
|
|
//
|
|
result = SendDeletePrinterMsgToClient(
|
|
DeviceList->devices[ofs].clientDeviceName);
|
|
|
|
//
|
|
// Clean up the notification object if one is registered.
|
|
//
|
|
if (DeviceList->devices[ofs].deviceSpecificData != NULL) {
|
|
|
|
PPRINTNOTIFYREC notifyRec =
|
|
(PPRINTNOTIFYREC)DeviceList->devices[ofs].deviceSpecificData;
|
|
ASSERT(notifyRec->notificationObject != NULL);
|
|
ASSERT(notifyRec->printerHandle != NULL);
|
|
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
notifyRec->notificationObject
|
|
);
|
|
FindClosePrinterChangeNotification(
|
|
notifyRec->notificationObject
|
|
);
|
|
|
|
ClosePrinter(notifyRec->printerHandle);
|
|
FREEMEM(notifyRec);
|
|
DeviceList->devices[ofs].deviceSpecificData = NULL;
|
|
}
|
|
|
|
//
|
|
// Remove it from the list of managed devices.
|
|
//
|
|
DRDEVLST_Remove(DeviceList, ofs);
|
|
}
|
|
else {
|
|
result = TRUE;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
HandlePrinterNameChangeNotification(
|
|
IN DWORD serverDeviceID,
|
|
IN LPWSTR printerName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle notification from the spooler that the name of a printer has changed.
|
|
This allows us to track these significant events:
|
|
|
|
-A printer automatically created by us has been assigned a device ID by the
|
|
spooler.
|
|
-A new printer has been manually added to the system and attached to one
|
|
of our redirected ports.
|
|
-A printer attached to one of our redirected ports has had its name changed.
|
|
|
|
Arguments:
|
|
|
|
serverDeviceID - Server-assigned device ID associated with the printer
|
|
name change.
|
|
printerName - New printer name.
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
HANDLE hPrinter = NULL;
|
|
BOOL result = TRUE;
|
|
PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ALL_ACCESS};
|
|
BOOL printerInList;
|
|
BOOL isNewPrinter;
|
|
DWORD ofs;
|
|
BOOL printerNameExists;
|
|
PWSTR portName;
|
|
DWORD printerNameOfs;
|
|
DWORD len;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterNameChangeNotification printer %ws.\n",
|
|
printerName));
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// See if we already have a matching printer name.
|
|
//
|
|
printerNameExists =
|
|
(DRDEVLST_FindByServerDeviceName(DeviceList, printerName,
|
|
&printerNameOfs)
|
|
&& (DeviceList->devices[printerNameOfs].deviceType ==
|
|
RDPDR_DTYP_PRINT));
|
|
|
|
//
|
|
// If a printer automatically created by us has been assigned a
|
|
// device ID by the spooler. In some cases, we may get a repeat
|
|
// printer name. That is okay because the ID should be the same.
|
|
//
|
|
if (printerNameExists) {
|
|
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:****Printer %ws has had its ID assigned to %ld.****\n",
|
|
DeviceList->devices[printerNameOfs].serverDeviceName,
|
|
serverDeviceID));
|
|
DeviceList->devices[printerNameOfs].serverDeviceID = serverDeviceID;
|
|
|
|
//
|
|
// Register a notification object with the printer, so we can
|
|
// be notified when its configuration changes. This change notification
|
|
// is registered for events that are not picked up by the global
|
|
// change notification object.
|
|
//
|
|
result = RegisterPrinterConfigChangeNotification(
|
|
serverDeviceID
|
|
);
|
|
}
|
|
//
|
|
// If a printer attached to one of our redirected ports has had
|
|
// its name changed.
|
|
//
|
|
else if (DRDEVLST_FindByServerDeviceID(
|
|
DeviceList,
|
|
serverDeviceID, &ofs
|
|
)) {
|
|
WCHAR *pBuf;
|
|
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:****Printer %ws has had its name changed to %ws.****\n",
|
|
DeviceList->devices[ofs].serverDeviceName,
|
|
printerName));
|
|
|
|
//
|
|
// Reallocate the server name field.
|
|
//
|
|
len = wcslen(printerName) + 1;
|
|
pBuf = REALLOCMEM(DeviceList->devices[ofs].serverDeviceName,
|
|
len * sizeof(WCHAR));
|
|
if (pBuf != NULL) {
|
|
DeviceList->devices[ofs].serverDeviceName = pBuf;
|
|
} else {
|
|
FREEMEM(DeviceList->devices[ofs].serverDeviceName);
|
|
DeviceList->devices[ofs].serverDeviceName = NULL;
|
|
}
|
|
|
|
if (DeviceList->devices[ofs].serverDeviceName != NULL) {
|
|
wcscpy(DeviceList->devices[ofs].serverDeviceName,
|
|
printerName);
|
|
|
|
//
|
|
// Send this information (printer name change) across to the client
|
|
//
|
|
DBGMSG(DBG_TRACE,("UMRDPPRN:clientDeviceID is %ld.\n",
|
|
DeviceList->devices[ofs].clientDeviceID ));
|
|
|
|
if (SendPrinterRenameToClient(
|
|
DeviceList->devices[ofs].clientDeviceName,
|
|
printerName
|
|
)) {
|
|
|
|
//
|
|
// Update the client name
|
|
//
|
|
pBuf = REALLOCMEM(DeviceList->devices[ofs].clientDeviceName,
|
|
len * sizeof(WCHAR));
|
|
if (pBuf != NULL) {
|
|
DeviceList->devices[ofs].clientDeviceName = pBuf;
|
|
} else {
|
|
FREEMEM(DeviceList->devices[ofs].clientDeviceName);
|
|
DeviceList->devices[ofs].clientDeviceName = NULL;
|
|
}
|
|
|
|
if (DeviceList->devices[ofs].clientDeviceName != NULL) {
|
|
wcscpy(DeviceList->devices[ofs].clientDeviceName,
|
|
printerName);
|
|
}
|
|
}
|
|
|
|
result = TRUE;
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR,("UMRDPPRN:Unable to allocate %ld bytes.\n",
|
|
len * sizeof(WCHAR)));
|
|
|
|
TsLogError(EVENT_NOTIFY_INSUFFICIENTRESOURCES, EVENTLOG_ERROR_TYPE,
|
|
0, NULL, __LINE__);
|
|
|
|
result = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
result = FALSE;
|
|
}
|
|
|
|
//
|
|
// Open the printer to get the associated port name.
|
|
//
|
|
if (result) {
|
|
result = OpenPrinter(printerName, &hPrinter, &defaults);
|
|
}
|
|
if (!result && !ShutdownFlag) {
|
|
//
|
|
// If the error is a result of a non-existent printer, the printer has
|
|
// probably been renamed and is pending delete, so this is ok.
|
|
//
|
|
if (GetLastError() == ERROR_INVALID_PRINTER_NAME) {
|
|
DBGMSG(DBG_WARN,
|
|
("UMRDPDPRN:Error opening %ws in refresh. Error: %ld. Probably ok.\n",
|
|
printerName, GetLastError()));
|
|
result = TRUE;
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPDPRN:Error opening %ws in refresh. Error: %ld.\n",
|
|
printerName, GetLastError()));
|
|
}
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Get the port name for the printer.
|
|
//
|
|
if (result) {
|
|
result = GetPrinterPortName(hPrinter, &portName);
|
|
if (!result) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPDPRN:GetPrinterPortName Failed. Error: %ld.\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
|
|
//
|
|
// If a new printer has been manually added to the system and
|
|
// attached to one of our redirected ports.
|
|
//
|
|
if (result) {
|
|
if (DRDEVLST_FindByServerDeviceName(DeviceList, portName, &ofs) &&
|
|
ISPRINTPORT(DeviceList->devices[ofs].deviceType)) {
|
|
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:****New printer %ws manually attached to %ws.****\n",
|
|
printerName, portName));
|
|
|
|
//
|
|
// Send the add printer message to the client. We don't care about
|
|
// the return status, since we can't do anything to recover from
|
|
// a failure to send the message to the client.
|
|
//
|
|
SendAddPrinterMsgToClient(
|
|
printerName,
|
|
PrinterInfo2Buf->pDriverName,
|
|
DeviceList->devices[ofs].preferredDosName
|
|
);
|
|
|
|
//
|
|
// Add the session number to the printer queue data to identify the printer
|
|
// as a TS printer. Don't care about the return value here, because its
|
|
// failure for a manually installed printer is not a critical error.
|
|
//
|
|
AddSessionIDToPrinterQueue(hPrinter, GETTHESESSIONID());
|
|
|
|
//
|
|
// Add the new printer to our list of managed printers.
|
|
//
|
|
result = DRDEVLST_Add(
|
|
DeviceList, RDPDR_INVALIDDEVICEID,
|
|
serverDeviceID,
|
|
RDPDR_DTYP_PRINT, printerName, printerName, "UNKNOWN"
|
|
);
|
|
|
|
//
|
|
// Register a notification object with the printer, so we can
|
|
// be notified when its configuration changes. This change notification
|
|
// is registered for events that are not picked up by the global
|
|
// change notification object.
|
|
//
|
|
if (result) {
|
|
RegisterPrinterConfigChangeNotification(
|
|
serverDeviceID
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CleanupAndExit:
|
|
//
|
|
// Close the printer.
|
|
//
|
|
if (hPrinter != NULL) {
|
|
ClosePrinter(hPrinter);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
RegisterPrinterConfigChangeNotification(
|
|
IN DWORD serverDeviceID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Register a change notification event with the spooler so that we are
|
|
notified when the configuration for a specific printer has been changed.
|
|
|
|
Arguments:
|
|
|
|
serverDeviceID - Server-side ID for the device that is used to
|
|
track the device in the device list.
|
|
|
|
Return Value:
|
|
|
|
TRUE on success. Otherwise, FALSE is returned.
|
|
|
|
--*/
|
|
{
|
|
DWORD offset;
|
|
BOOL result;
|
|
PRINTER_DEFAULTS printerDefaults;
|
|
PPRINTNOTIFYREC notifyRec;
|
|
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:RegisterPrinterConfigChangeNotification id %ld.\n",
|
|
serverDeviceID)
|
|
);
|
|
|
|
//
|
|
// Find the printer in the device list.
|
|
//
|
|
result = DRDEVLST_FindByServerDeviceID(
|
|
DeviceList, serverDeviceID, &offset
|
|
);
|
|
|
|
//
|
|
// Register a notification object with the printer, so we can
|
|
// be notified when its configuration changes. This change notification
|
|
// is registered for events that are not picked up by the global
|
|
// change notification object.
|
|
//
|
|
if (result && (DeviceList->devices[offset].deviceSpecificData == NULL)) {
|
|
LPWSTR name = DeviceList->devices[offset].serverDeviceName;
|
|
|
|
printerDefaults.pDatatype = NULL;
|
|
printerDefaults.pDevMode = NULL;
|
|
printerDefaults.DesiredAccess = PRINTER_ALL_ACCESS;
|
|
|
|
//
|
|
// Allocate a new notification record.
|
|
//
|
|
notifyRec = ALLOCMEM(sizeof(PRINTNOTIFYREC));
|
|
result = (notifyRec != NULL);
|
|
if (result) {
|
|
notifyRec->printerHandle = NULL;
|
|
notifyRec->notificationObject = NULL;
|
|
notifyRec->serverDeviceID = serverDeviceID;
|
|
result = OpenPrinter(name, ¬ifyRec->printerHandle,
|
|
&printerDefaults);
|
|
}
|
|
|
|
//
|
|
// Register the notification.
|
|
//
|
|
if (result) {
|
|
notifyRec->notificationObject =
|
|
FindFirstPrinterChangeNotification(
|
|
notifyRec->printerHandle,
|
|
PRINTER_CHANGE_PRINTER_DRIVER |
|
|
PRINTER_CHANGE_DELETE_PRINTER, 0,
|
|
NULL
|
|
);
|
|
if (notifyRec->notificationObject != NULL) {
|
|
result =
|
|
WTBLOBJ_AddWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
notifyRec,
|
|
notifyRec->notificationObject,
|
|
SinglePrinterNotifyObjectSignaled
|
|
) == ERROR_SUCCESS;
|
|
}
|
|
else {
|
|
DBGMSG(
|
|
DBG_ERROR,
|
|
("UMRDPPRN:FindFirstPrinterChangeNotification failed for %ws: %ld.\n",
|
|
name, GetLastError())
|
|
);
|
|
result = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: Can't open printer %ws: %ld.\n",
|
|
name, GetLastError()));
|
|
}
|
|
|
|
//
|
|
// Record the notification record or clean up.
|
|
//
|
|
if (result) {
|
|
DeviceList->devices[offset].deviceSpecificData = notifyRec;
|
|
}
|
|
else if (notifyRec != NULL) {
|
|
if (notifyRec->notificationObject != NULL) {
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
notifyRec->notificationObject
|
|
);
|
|
FindClosePrinterChangeNotification(
|
|
notifyRec->notificationObject
|
|
);
|
|
}
|
|
|
|
if (notifyRec->printerHandle != NULL) {
|
|
ClosePrinter(notifyRec->printerHandle);
|
|
}
|
|
|
|
FREEMEM(notifyRec);
|
|
}
|
|
}
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:RegisterPrinterConfigChangeNotification done.\n"));
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
SendAddPrinterMsgToClient(
|
|
IN PCWSTR printerName,
|
|
IN PCWSTR driverName,
|
|
IN PCSTR dosDevicePort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send an add printer message to the client.
|
|
|
|
Arguments:
|
|
|
|
printerName - Name of new printer.
|
|
driverName - Name of printer driver.
|
|
portName - Client-side dos device port name.
|
|
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
PRDPDR_PRINTER_CACHEDATA_PACKET cachedDataPacket;
|
|
DWORD cachedDataPacketSize;
|
|
PRDPDR_PRINTER_ADD_CACHEDATA cachedData;
|
|
BOOL result;
|
|
DWORD driverSz, printerSz;
|
|
PWSTR str;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SendAddPrinterMsgToClient.\n"));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SendAddPrinterMsgToClient printer name is %ws.\n",
|
|
printerName));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SendAddPrinterMsgToClient driver name is %ws.\n",
|
|
driverName));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SendAddPrinterMsgToClient dos device port is %s.\n",
|
|
dosDevicePort));
|
|
|
|
//
|
|
// Calculate the message size.
|
|
//
|
|
driverSz = ((wcslen(driverName) + 1) * sizeof(WCHAR));
|
|
printerSz = ((wcslen(printerName) + 1) * sizeof(WCHAR));
|
|
cachedDataPacketSize = sizeof(RDPDR_PRINTER_CACHEDATA_PACKET) +
|
|
sizeof(RDPDR_PRINTER_ADD_CACHEDATA) +
|
|
driverSz + printerSz;
|
|
|
|
//
|
|
// Allocate the message.
|
|
//
|
|
cachedDataPacket = (PRDPDR_PRINTER_CACHEDATA_PACKET)ALLOCMEM(
|
|
cachedDataPacketSize
|
|
);
|
|
result = (cachedDataPacket != NULL);
|
|
|
|
if (result) {
|
|
//
|
|
// Set up the packet.
|
|
//
|
|
cachedDataPacket->Header.PacketId = DR_PRN_CACHE_DATA;
|
|
cachedDataPacket->Header.Component = RDPDR_CTYP_PRN;
|
|
cachedDataPacket->EventId = RDPDR_ADD_PRINTER_EVENT;
|
|
|
|
//
|
|
// Set up the cached data.
|
|
//
|
|
cachedData = (PRDPDR_PRINTER_ADD_CACHEDATA)(
|
|
(PBYTE)cachedDataPacket +
|
|
sizeof(RDPDR_PRINTER_CACHEDATA_PACKET)
|
|
);
|
|
strcpy(cachedData->PortDosName, dosDevicePort);
|
|
cachedData->PnPNameLen = 0;
|
|
cachedData->DriverLen = driverSz;
|
|
cachedData->PrinterNameLen = printerSz;
|
|
cachedData->CachedFieldsLen = 0;
|
|
|
|
//
|
|
// Add the driver name.
|
|
//
|
|
str = (PWSTR)((PBYTE)cachedData + sizeof(RDPDR_PRINTER_ADD_CACHEDATA));
|
|
wcscpy(str, driverName);
|
|
|
|
//
|
|
// Add the printer name.
|
|
//
|
|
str = str + driverSz/2;
|
|
wcscpy(str, printerName);
|
|
|
|
//
|
|
// Send the message to the client.
|
|
//
|
|
result = UMRDPDR_SendMessageToClient(
|
|
cachedDataPacket,
|
|
cachedDataPacketSize
|
|
);
|
|
|
|
// Release the buffer.
|
|
FREEMEM(cachedDataPacket);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
SendDeletePrinterMsgToClient(
|
|
IN PCWSTR printerName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send a delete printer message to the client.
|
|
|
|
Arguments:
|
|
|
|
printerName - Client-recognized name of printer to delete.
|
|
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
PRDPDR_PRINTER_CACHEDATA_PACKET cachedDataPacket;
|
|
DWORD cachedDataPacketSize;
|
|
PRDPDR_PRINTER_DELETE_CACHEDATA cachedData;
|
|
BOOL result;
|
|
DWORD printerSz;
|
|
PWSTR str;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SendDeletePrinterMsgToClient printer name is %ws.\n",
|
|
printerName));
|
|
|
|
//
|
|
// Calculate the message size.
|
|
//
|
|
printerSz = ((wcslen(printerName) + 1) * sizeof(WCHAR));
|
|
cachedDataPacketSize = sizeof(RDPDR_PRINTER_CACHEDATA_PACKET) +
|
|
sizeof(RDPDR_PRINTER_DELETE_CACHEDATA) +
|
|
printerSz;
|
|
|
|
//
|
|
// Allocate the message.
|
|
//
|
|
cachedDataPacket = (PRDPDR_PRINTER_CACHEDATA_PACKET)ALLOCMEM(
|
|
cachedDataPacketSize
|
|
);
|
|
result = (cachedDataPacket != NULL);
|
|
if (result) {
|
|
//
|
|
// Set up the packet.
|
|
//
|
|
cachedDataPacket->Header.PacketId = DR_PRN_CACHE_DATA;
|
|
cachedDataPacket->Header.Component = RDPDR_CTYP_PRN;
|
|
cachedDataPacket->EventId = RDPDR_DELETE_PRINTER_EVENT;
|
|
|
|
//
|
|
// Set up the cached data.
|
|
//
|
|
cachedData = (PRDPDR_PRINTER_DELETE_CACHEDATA)(
|
|
(PBYTE)cachedDataPacket +
|
|
sizeof(RDPDR_PRINTER_CACHEDATA_PACKET)
|
|
);
|
|
cachedData->PrinterNameLen = printerSz;
|
|
|
|
//
|
|
// Add the printer name.
|
|
//
|
|
str = (PWSTR)((PBYTE)cachedData + sizeof(RDPDR_PRINTER_DELETE_CACHEDATA));
|
|
wcscpy(str, printerName);
|
|
|
|
//
|
|
// Send the message to the client.
|
|
//
|
|
result = UMRDPDR_SendMessageToClient(
|
|
cachedDataPacket,
|
|
cachedDataPacketSize
|
|
);
|
|
|
|
// Release the buffer.
|
|
FREEMEM(cachedDataPacket);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
RegisterSerialPort(
|
|
IN PCWSTR portName,
|
|
IN PCWSTR devicePath
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Register a serial port device by creating the symbolic link
|
|
|
|
Arguments:
|
|
|
|
portName - Port name for the serial device
|
|
devicePath - NT device path for the symbolic link
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD SymLinkNameLen;
|
|
size_t RemainingCharCount;
|
|
PWSTR pNtSymLinkName, buffer;
|
|
UNICODE_STRING SymLinkName, SymLinkValue;
|
|
HANDLE SymLinkHandle;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
NTSTATUS Status;
|
|
ULONG dwErrorCode, LUIDDeviceMapsEnabled;
|
|
BOOL fImpersonated = FALSE, fLUIDDeviceMapsEnabled;
|
|
BOOL result = TRUE;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:RegisterSerialPort with port %ws.\n", portName));
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:RegisterSerialPort with device path %ws.\n", devicePath));
|
|
|
|
buffer = NULL;
|
|
|
|
//
|
|
// Check if LUID DosDevices maps are enabled
|
|
//
|
|
Status = NtQueryInformationProcess( NtCurrentProcess(),
|
|
ProcessLUIDDeviceMapsEnabled,
|
|
&LUIDDeviceMapsEnabled,
|
|
sizeof(LUIDDeviceMapsEnabled),
|
|
NULL
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
fLUIDDeviceMapsEnabled = (LUIDDeviceMapsEnabled != 0);
|
|
}
|
|
else {
|
|
fLUIDDeviceMapsEnabled = FALSE;
|
|
}
|
|
|
|
//
|
|
// If LUID Device Maps are enabled,
|
|
// We need to impersonate the logged on user in order to create
|
|
// the symbolic links in the correct device map
|
|
// If LUID Device Maps are disabled,
|
|
// we should not impersonate the logged on user in order to delete
|
|
// any existing symbolic links
|
|
//
|
|
if (fLUIDDeviceMapsEnabled == TRUE) {
|
|
fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser);
|
|
|
|
if (!fImpersonated) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error impersonate user in function RegisterSerialPort. Error: %ld\n",
|
|
GetLastError()));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// length of ( portName ) + Length of("\\??\\") + UNICODE NULL
|
|
RemainingCharCount = wcslen(portName) + DEVICE_MAP_NAME_COUNT + 1;
|
|
|
|
SymLinkNameLen = RemainingCharCount * sizeof( WCHAR );
|
|
|
|
pNtSymLinkName = (PWSTR) ALLOCMEM( SymLinkNameLen );
|
|
|
|
if (pNtSymLinkName == NULL) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error allocating memory in function RegisterSerialPort. Error: %ld\n",
|
|
GetLastError()));
|
|
return( FALSE );
|
|
}
|
|
|
|
//
|
|
// Copy \??\ to the symbolic link name
|
|
//
|
|
wcsncpy( pNtSymLinkName, DEVICE_MAP_NAME, RemainingCharCount );
|
|
|
|
RemainingCharCount = RemainingCharCount - DEVICE_MAP_NAME_COUNT;
|
|
|
|
//
|
|
// Append the portname to the symbolic link name
|
|
//
|
|
wcsncat( pNtSymLinkName, portName, RemainingCharCount );
|
|
|
|
RtlInitUnicodeString(&SymLinkName, (PCWSTR)pNtSymLinkName);
|
|
|
|
RtlInitUnicodeString(&SymLinkValue, devicePath);
|
|
|
|
InitializeObjectAttributes( &ObjectAttributes,
|
|
&SymLinkName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL );
|
|
|
|
Status = NtCreateSymbolicLinkObject( &SymLinkHandle,
|
|
SYMBOLIC_LINK_ALL_ACCESS,
|
|
&ObjectAttributes,
|
|
&SymLinkValue
|
|
);
|
|
|
|
//
|
|
// If LUID device maps are disabled, then CsrPopulateDosDevices() would
|
|
// have copied the global symbolic link into this TS device map, which
|
|
// would cause the create to fail, so we need to delete the copy before
|
|
// creating our symbolic link
|
|
//
|
|
if (Status == STATUS_OBJECT_NAME_COLLISION) {
|
|
Status = NtOpenSymbolicLinkObject( &SymLinkHandle,
|
|
SYMBOLIC_LINK_QUERY | DELETE,
|
|
&ObjectAttributes
|
|
);
|
|
|
|
if (NT_SUCCESS ( Status)) {
|
|
UNICODE_STRING SymLinkString;
|
|
unsigned SymLinkValueLen, ReturnedLength, bufLen;
|
|
|
|
SymLinkValueLen = wcslen(devicePath);
|
|
|
|
// Find how much buffer is required for the symlink value
|
|
SymLinkString.Buffer = NULL;
|
|
SymLinkString.Length = 0;
|
|
SymLinkString.MaximumLength = 0;
|
|
ReturnedLength = 0;
|
|
Status = NtQuerySymbolicLinkObject( SymLinkHandle,
|
|
&SymLinkString,
|
|
&ReturnedLength
|
|
);
|
|
|
|
if (Status != STATUS_BUFFER_TOO_SMALL) {
|
|
ReturnedLength = 0;
|
|
}
|
|
|
|
bufLen = SymLinkValueLen * sizeof(WCHAR) + sizeof(UNICODE_NULL) * 2 + ReturnedLength;
|
|
buffer = (PWSTR) ALLOCMEM( bufLen );
|
|
|
|
if (buffer == NULL) {
|
|
NtClose(SymLinkHandle);
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error allocating memory in function RegisterSerialPort. Error: %ld\n",
|
|
GetLastError()));
|
|
return( FALSE );
|
|
}
|
|
|
|
// Setup the devicepath as the first entry
|
|
wcscpy(buffer, devicePath);
|
|
buffer[SymLinkValueLen] = UNICODE_NULL;
|
|
|
|
if (ReturnedLength > 0) {
|
|
// Get the existing symlink
|
|
SymLinkString.Buffer = buffer + SymLinkValueLen + 1;
|
|
SymLinkString.Buffer[0] = UNICODE_NULL;
|
|
SymLinkString.MaximumLength = (USHORT)(bufLen - (SymLinkValueLen + 1) * sizeof(WCHAR));
|
|
SymLinkString.Length = 0;
|
|
ReturnedLength = 0;
|
|
|
|
Status = NtQuerySymbolicLinkObject( SymLinkHandle,
|
|
&SymLinkString,
|
|
&ReturnedLength
|
|
);
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
if (ReturnedLength > 2 && (SymLinkString.Buffer[ReturnedLength/sizeof(WCHAR) - 2] != UNICODE_NULL) ) {
|
|
// Make sure we always end with an UNICODE_NULL
|
|
SymLinkString.Buffer[ReturnedLength/sizeof(WCHAR)] = UNICODE_NULL;
|
|
ReturnedLength += sizeof(UNICODE_NULL);
|
|
}
|
|
}
|
|
else {
|
|
ReturnedLength = 0;
|
|
}
|
|
}
|
|
|
|
// Setup the symlink string
|
|
SymLinkString.Buffer = buffer;
|
|
SymLinkString.Length = (USHORT)(SymLinkValueLen * sizeof(WCHAR));
|
|
SymLinkString.MaximumLength = (USHORT)((SymLinkValueLen + 1) * sizeof(WCHAR) + ReturnedLength);
|
|
|
|
Status = NtMakeTemporaryObject( SymLinkHandle );
|
|
NtClose( SymLinkHandle );
|
|
SymLinkHandle = NULL;
|
|
|
|
if (NT_SUCCESS ( Status)) {
|
|
|
|
Status = NtCreateSymbolicLinkObject( &SymLinkHandle,
|
|
SYMBOLIC_LINK_ALL_ACCESS,
|
|
&ObjectAttributes,
|
|
&SymLinkString
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Revert the thread token to self
|
|
if (fImpersonated) {
|
|
RevertToSelf();
|
|
}
|
|
|
|
//
|
|
// After the revert to Local System
|
|
//
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = NtMakePermanentObject( SymLinkHandle ); // must be Local System
|
|
NtClose ( SymLinkHandle );
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
result = TRUE;
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:RegisterSerialPort with port %ws succeeded.\n", portName));
|
|
}
|
|
else {
|
|
dwErrorCode = RtlNtStatusToDosError( Status );
|
|
SetLastError( dwErrorCode );
|
|
|
|
result = FALSE;
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:RegisterSerialPort with port %ws failed: %x.\n",
|
|
portName, GetLastError()));
|
|
}
|
|
|
|
|
|
//
|
|
// Cleanup the memory that we allocated
|
|
//
|
|
if (pNtSymLinkName != NULL) {
|
|
FREEMEM(pNtSymLinkName);
|
|
}
|
|
|
|
if (buffer != NULL) {
|
|
FREEMEM(buffer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
UMRDPPRN_HandlePrintPortAnnounceEvent(
|
|
IN PRDPDR_PORTDEVICE_SUB pPortAnnounce
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle a printer port device announce event from the "dr" by
|
|
adding a record for the device to the list of installed devices.
|
|
|
|
Arguments:
|
|
|
|
pPortAnnounce - Port device announce event.
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:UMRDPPRN_HandlePrintPortAnnounceEvent with port %ws.\n",
|
|
pPortAnnounce->portName));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPDRN:Preferred DOS name is %s.\n",
|
|
pPortAnnounce->deviceFields.PreferredDosName));
|
|
|
|
ASSERT( ISPRINTPORT(pPortAnnounce->deviceFields.DeviceType) );
|
|
|
|
if (pPortAnnounce->deviceFields.DeviceType == RDPDR_DTYP_SERIAL ||
|
|
pPortAnnounce->deviceFields.DeviceType == RDPDR_DTYP_PARALLEL) {
|
|
|
|
WCHAR serverDevicePath[MAX_PATH];
|
|
|
|
// Query the original symbolic link for the serial port and save it for restore
|
|
// later
|
|
serverDevicePath[0] = L'\0';
|
|
if (QueryDosDevice(pPortAnnounce->portName, serverDevicePath, MAX_PATH) != 0) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:QueryDosDevice on port: %ws, returns path: %ws.\n",
|
|
pPortAnnounce->portName, serverDevicePath));
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:QueryDosDevice on port: %ws returns error: %x.\n",
|
|
pPortAnnounce->portName, GetLastError()));
|
|
}
|
|
|
|
// Register the new symbolic link name
|
|
RegisterSerialPort(pPortAnnounce->portName, pPortAnnounce->devicePath);
|
|
|
|
// Just record the port so we can remember it for later.
|
|
// We save the new serial symbolic link name in client device name and
|
|
// the original symbolic link in server device name
|
|
return DRDEVLST_Add(
|
|
DeviceList,
|
|
pPortAnnounce->deviceFields.DeviceId,
|
|
UMRDPDR_INVALIDSERVERDEVICEID,
|
|
pPortAnnounce->deviceFields.DeviceType,
|
|
serverDevicePath,
|
|
pPortAnnounce->devicePath,
|
|
pPortAnnounce->deviceFields.PreferredDosName
|
|
);
|
|
}
|
|
else {
|
|
if (!PrintingModuleInitialized) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Just record the port so we can remember it for later.
|
|
return DRDEVLST_Add(
|
|
DeviceList,
|
|
pPortAnnounce->deviceFields.DeviceId,
|
|
UMRDPDR_INVALIDSERVERDEVICEID,
|
|
pPortAnnounce->deviceFields.DeviceType,
|
|
pPortAnnounce->portName,
|
|
pPortAnnounce->portName,
|
|
pPortAnnounce->deviceFields.PreferredDosName
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
UMRDPPRN_DeleteSerialLink(
|
|
UCHAR *preferredDosName,
|
|
WCHAR *ServerDeviceName,
|
|
WCHAR *ClientDeviceName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Delete the new symbolic link on disconnect/logoff and restore the old one
|
|
as necessary
|
|
|
|
Arguments:
|
|
|
|
preferredDosName - the port name in ANSI char
|
|
ServerDeviceName - the original serial link symbolic path
|
|
ClientDeviceName - the new serial link symbolic path
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
ULONG LUIDDeviceMapsEnabled;
|
|
BOOL fImpersonated = FALSE, fLUIDDeviceMapsEnabled;
|
|
WCHAR PortNameBuff[PREFERRED_DOS_NAME_SIZE];
|
|
NTSTATUS Status;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:UMRDPPRN_DeleteSerialLink\n"));
|
|
|
|
// Assemble the port name from preferred dos name
|
|
PortNameBuff[0] = L'\0';
|
|
MultiByteToWideChar(CP_ACP, 0, preferredDosName, -1, PortNameBuff, PREFERRED_DOS_NAME_SIZE);
|
|
|
|
//
|
|
// Check if LUID DosDevices maps are enabled
|
|
//
|
|
Status = NtQueryInformationProcess( NtCurrentProcess(),
|
|
ProcessLUIDDeviceMapsEnabled,
|
|
&LUIDDeviceMapsEnabled,
|
|
sizeof(LUIDDeviceMapsEnabled),
|
|
NULL
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
fLUIDDeviceMapsEnabled = (LUIDDeviceMapsEnabled != 0);
|
|
}
|
|
else {
|
|
fLUIDDeviceMapsEnabled = FALSE;
|
|
}
|
|
|
|
//
|
|
// If LUID Device Maps are enabled,
|
|
// We need to impersonate the logged on user in order to delete
|
|
// the symbolic links from the correct device map
|
|
// If LUID Device Maps are disabled,
|
|
// we should not impersonate the logged on user in order to delete
|
|
// any existing symbolic links
|
|
//
|
|
if (fLUIDDeviceMapsEnabled == TRUE) {
|
|
fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser);
|
|
|
|
if (!fImpersonated) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error impersonate user in function UMRDPPRN_DeleteSerialLink. Error: %ld\n",
|
|
GetLastError()));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (PortNameBuff[0] != L'\0') {
|
|
// Just need to delete the new symbolic link for this session
|
|
if (DefineDosDevice(DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE,
|
|
PortNameBuff,
|
|
ClientDeviceName) != 0) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:Delete port %ws with link %ws succeeded.\n",
|
|
PortNameBuff, ClientDeviceName));
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:Delete port: %ws with link %ws failed: %x\n",
|
|
PortNameBuff, ClientDeviceName, GetLastError()));
|
|
}
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:UMRDPPRN_DeleteSerialLink failed to get the port name\n"));
|
|
}
|
|
|
|
// Delete the serial registry entry if on PTS box
|
|
|
|
if (fRunningOnPTS) {
|
|
DWORD rc;
|
|
HKEY regKey;
|
|
|
|
rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TSSERIALDEVICEMAP, 0,
|
|
KEY_ALL_ACCESS, ®Key);
|
|
if (rc == ERROR_SUCCESS) {
|
|
RegDeleteValue(regKey, PortNameBuff);
|
|
RegCloseKey(regKey);
|
|
}
|
|
}
|
|
|
|
// Revert the thread token to self
|
|
if (fImpersonated) {
|
|
RevertToSelf();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
WCHAR *ANSIToUnicode(
|
|
IN LPCSTR ansiString,
|
|
IN UINT codePage
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert an ANSI string to Unicode.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
Returns the converted string or NULL on error.. It is up to the caller to
|
|
release this string.
|
|
|
|
--*/
|
|
|
|
{
|
|
int numChars;
|
|
PWSTR buf=NULL;
|
|
|
|
//
|
|
// Convert the driver name.
|
|
//
|
|
// First, get the required buffer size.
|
|
numChars = MultiByteToWideChar(
|
|
codePage, 0, ansiString,
|
|
-1, NULL, 0
|
|
);
|
|
|
|
// Allocate the buffer.
|
|
buf = (PWSTR)ALLOCMEM((numChars + 1) * sizeof(WCHAR));
|
|
if (buf != NULL) {
|
|
buf[0] = L'\0';
|
|
numChars = MultiByteToWideChar(
|
|
codePage, 0, ansiString,
|
|
-1, buf, numChars
|
|
);
|
|
// Find out if the conversion succeeded.
|
|
|
|
if ((numChars != 0) || !ansiString[0]) {
|
|
return buf;
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error converting to Unicode. Error: %ld\n",
|
|
GetLastError()));
|
|
FREEMEM(buf);
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error allocating memory in function AnsiToUNICODE. Error: %ld\n",
|
|
GetLastError()));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
InstallPrinter(
|
|
IN PCWSTR portName,
|
|
IN PCWSTR driverName,
|
|
IN PWSTR printerNameBuf,
|
|
IN DWORD cchPrintNameBuf,
|
|
IN BOOL cachedDataExists,
|
|
OUT BOOL *triggerConfigChangeEvent
|
|
)
|
|
/*++
|
|
|
|
|
|
--*/
|
|
{
|
|
INT_PTR status = ERROR_SUCCESS; // PnpInterfaceFunc() returns INT_PTR
|
|
TAdvInfInstall tii;
|
|
TParameterBlock tpb;
|
|
PSECURITY_DESCRIPTOR psd;
|
|
PSID pSid = NULL;
|
|
BOOL daclPresent;
|
|
PACL dacl;
|
|
BOOL daclDefaulted;
|
|
|
|
DBGMSG(DBG_WARN,
|
|
("UMRDPPRN:InstallPrinter portName %ws, driverName %ws, printerName %ws\n",
|
|
portName, driverName, printerNameBuf));
|
|
|
|
*triggerConfigChangeEvent = FALSE;
|
|
|
|
//
|
|
// Get the user sid.
|
|
//
|
|
if ((pSid = TSNUTL_GetUserSid(UMRPDPPRN_TokenForLoggedOnUser)) == NULL) {
|
|
status = GetLastError();
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: Failed to get user SID: %ld\n",
|
|
status));
|
|
goto CLEANUPANDEXIT;
|
|
}
|
|
|
|
//
|
|
// Get the default printer security descriptor.
|
|
//
|
|
psd = RDPDRUTL_CreateDefaultPrinterSecuritySD(pSid);
|
|
if (psd == NULL) {
|
|
status = GetLastError();
|
|
goto CLEANUPANDEXIT;
|
|
}
|
|
|
|
tii.cbSize = sizeof(tii);
|
|
tii.pszModelName = driverName;
|
|
tii.pszServerName = NULL;
|
|
tii.pszInfName = PrinterInfPath;
|
|
tii.pszPortName = portName;
|
|
tii.pszPrinterNameBuffer = printerNameBuf;
|
|
tii.cchPrinterName = cchPrintNameBuf;
|
|
tii.pszSourcePath = NULL;
|
|
tii.dwFlags = kPnPInterface_Quiet |
|
|
kPnPInterface_NoShare |
|
|
kPnpInterface_UseExisting |
|
|
kPnpInterface_HydraSpecific;
|
|
tii.dwAttributes = PRINTER_ATTRIBUTE_TS;
|
|
tii.pSecurityDescriptor = psd;
|
|
|
|
|
|
//
|
|
// If cached data does not exist, then OR in the flag that
|
|
// enables printui to set a default ICM color profile, for
|
|
// color printers. Note that there is no additional overhead
|
|
// for non-color printers.
|
|
//
|
|
if (!cachedDataExists) {
|
|
tii.dwFlags |= kPnPInterface_InstallColorProfiles;
|
|
}
|
|
|
|
memset(&tpb, 0, sizeof(tpb));
|
|
tpb.pAdvInfInstall = &tii;
|
|
|
|
status = PnpInterfaceFunc(kAdvInfInstall, &tpb);
|
|
|
|
//
|
|
// The configuration info needs to be cached on the client if
|
|
// the printer is color and we didn't have any cached data to begin
|
|
// with. This is a performance optimization so we don't need to
|
|
// create a color profile for the remote printer each time we log in.
|
|
//
|
|
if (status == ERROR_SUCCESS) {
|
|
*triggerConfigChangeEvent = !cachedDataExists &&
|
|
(tii.dwOutFlags & kAdvInf_ColorPrinter);
|
|
}
|
|
|
|
//
|
|
// Release the Security Descriptor.
|
|
//
|
|
LocalFree(psd);
|
|
|
|
CLEANUPANDEXIT:
|
|
|
|
if (pSid != NULL) FREEMEM(pSid);
|
|
|
|
DBGMSG(DBG_WARN,("UMRDPPRN:InstallPrinter returns %ld\n", status));
|
|
|
|
return (DWORD)status;
|
|
}
|
|
|
|
VOID
|
|
TriggerConfigChangeTimer()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set the config change timer callback to fire 1 time.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LARGE_INTEGER li;
|
|
|
|
if (g_fTimerSet == FALSE) {
|
|
|
|
li.QuadPart = Int32x32To64(CONFIG_WAIT_PERIOD, -10000); // 30 seconds (in nano second units)
|
|
if (SetWaitableTimer(WaitableTimer,
|
|
&li,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
FALSE)) {
|
|
g_fTimerSet = TRUE;
|
|
}
|
|
else {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SetWaitableTimer Failed. Error: %ld.\n", GetLastError()));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
InstallPrinterWithPortName(
|
|
IN DWORD deviceID,
|
|
IN HANDLE hTokenForLoggedOnUser,
|
|
IN BOOL bSetDefault,
|
|
IN ULONG ulFlags,
|
|
IN PCWSTR portName,
|
|
IN PCWSTR driverName,
|
|
IN PCWSTR printerName,
|
|
IN PCWSTR clientComputerName,
|
|
IN PBYTE cacheData,
|
|
IN DWORD cacheDataLen
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Install the printing device.
|
|
|
|
Arguments:
|
|
|
|
deviceID - Device identifier assigned by kernel mode component and client.
|
|
hTokenForLoggedOnUser - Logged on user token.
|
|
portName - Name of printer port.
|
|
driverName - Printer driver name. (eg. AGFA-AccuSet v52.3)
|
|
printerName - Printer name. This function appends "/Session X/Computer Name"
|
|
clientComputerName - Name of client computer.
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
WCHAR printerNameBuf[MAX_PATH+1];
|
|
size_t printerNameLen;
|
|
DWORD status = ERROR_SUCCESS;
|
|
WCHAR errorBuf[256];
|
|
HANDLE hPrinter;
|
|
TS_PRINTER_NAMES printerNames;
|
|
PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ALL_ACCESS};
|
|
BOOL queueCreated = FALSE;
|
|
DWORD requiredSize;
|
|
BOOL clientDriverMapped;
|
|
WCHAR *parms[2];
|
|
BOOL triggerConfigChangeEvent;
|
|
DWORD ofs;
|
|
BOOL printerNameExists;
|
|
|
|
#if DBG
|
|
DWORD i;
|
|
#endif
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:InstallPrinterWithPortName.\n"));
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:Port name is %ws driver name is %ws\n", portName, driverName));
|
|
|
|
#ifndef UNICODE
|
|
ASSERT(0);
|
|
#endif
|
|
|
|
//
|
|
// format printer name
|
|
//
|
|
printerNames.ulTempLen = sizeof(printerNames.szTemp)/sizeof(printerNames.szTemp[0]);
|
|
printerNames.pszFullName = printerName;
|
|
printerNames.pszCurrentClient = clientComputerName;
|
|
printerNames.pszServer = NULL;
|
|
printerNames.pszClient = NULL;
|
|
printerNames.pszPrinter = NULL;
|
|
|
|
FormatPrinterName(printerNameBuf,
|
|
sizeof(printerNameBuf)/sizeof(printerNameBuf[0]),
|
|
ulFlags,
|
|
&printerNames);
|
|
|
|
printerNameLen = wcslen(printerNameBuf);
|
|
|
|
//
|
|
// Delete the printer if it already exists.
|
|
//
|
|
if (OpenPrinter(printerNameBuf, &hPrinter, &defaults)) {
|
|
|
|
DBGMSG(DBG_WARN,
|
|
("UMRDPPRN:Deleting existing printer %ws in InstallPrinterWithPortName!\n",
|
|
printerNameBuf));
|
|
|
|
if (!SetPrinter(hPrinter, 0, NULL, PRINTER_CONTROL_PURGE) ||
|
|
!DeletePrinter(hPrinter)) {
|
|
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Error deleting existing printer %ws: %08X!\n",
|
|
printerNameBuf,GetLastError()));
|
|
|
|
ClosePrinter(hPrinter);
|
|
return FALSE;
|
|
}
|
|
else {
|
|
ClosePrinter(hPrinter);
|
|
hPrinter = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
else {
|
|
hPrinter = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
status = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We want to check if driver already install first because
|
|
// if we simply dump installation of a nt4 or win9x driver
|
|
// to print UI, it will takes a long time for it to find out
|
|
// that driver does not exist, on other case, if a OEM driver
|
|
// already install, we also want to use it.
|
|
//
|
|
|
|
//
|
|
// MapClientPrintDriverName() will try out with upgrade infs file
|
|
// first, so if NT4/Win9x driver (not in ntprintf.inf), we will
|
|
// pick up mapping first.
|
|
//
|
|
status = PrinterDriverInstalled( driverName );
|
|
|
|
if( ERROR_SHUTDOWN_IN_PROGRESS == status ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
if( ERROR_SUCCESS == status ) {
|
|
|
|
//
|
|
// Driver already install. Try installing the printer. If this fails,
|
|
// driver must be blocked.
|
|
//
|
|
status = InstallPrinter(
|
|
portName,
|
|
driverName,
|
|
printerNameBuf,
|
|
sizeof(printerNameBuf) / sizeof(printerNameBuf[0]),
|
|
cacheDataLen > 0,
|
|
&triggerConfigChangeEvent
|
|
);
|
|
|
|
if( ERROR_SUCCESS == status ) {
|
|
queueCreated = TRUE;
|
|
}
|
|
|
|
if( ShutdownFlag ) {
|
|
// overwrite last error since we are shutting down
|
|
status = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if( ERROR_SUCCESS != status ) {
|
|
|
|
//
|
|
// Driver not installed or failed to installed printer with
|
|
// driver already exist on system, go thru mapping process
|
|
//
|
|
clientDriverMapped = MapClientPrintDriverName(
|
|
driverName,
|
|
&MappedDriverNameBuf,
|
|
&MappedDriverNameBufSize
|
|
);
|
|
|
|
status = InstallPrinter(
|
|
portName,
|
|
(clientDriverMapped) ? MappedDriverNameBuf : driverName,
|
|
printerNameBuf,
|
|
sizeof(printerNameBuf) / sizeof(printerNameBuf[0]),
|
|
cacheDataLen > 0,
|
|
&triggerConfigChangeEvent
|
|
);
|
|
|
|
if( ERROR_SUCCESS == status ) {
|
|
queueCreated = TRUE;
|
|
}
|
|
}
|
|
|
|
if( ShutdownFlag ) {
|
|
// overwrite last error since we are shutting down
|
|
status = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Log an error message if the driver wasn't found
|
|
//
|
|
if (!queueCreated) {
|
|
ASSERT( status != ERROR_SUCCESS );
|
|
if ((status == ERROR_FILE_NOT_FOUND) || (status == ERROR_PATH_NOT_FOUND)) {
|
|
ASSERT((sizeof(parms)/sizeof(WCHAR *)) >= 2);
|
|
parms[0] = (WCHAR *)driverName;
|
|
parms[1] = (WCHAR *)printerName;
|
|
TsLogError(EVENT_NOTIFY_DRIVER_NOT_FOUND, EVENTLOG_ERROR_TYPE, 2,
|
|
parms, __LINE__);
|
|
}
|
|
else if (status == ERROR_UNKNOWN_PRINTER_DRIVER) {
|
|
ASSERT((sizeof(parms)/sizeof(WCHAR *)) >= 2);
|
|
parms[0] = (WCHAR *)driverName;
|
|
parms[1] = (WCHAR *)printerName;
|
|
TsLogError(EVENT_NOTIFY_UNKNOWN_PRINTER_DRIVER, EVENTLOG_ERROR_TYPE, 2,
|
|
parms, __LINE__);
|
|
}
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:Printui func failed with status %08x.\n", status));
|
|
}
|
|
|
|
//
|
|
// Set the new printer as the default printer, after saving the
|
|
// current printer context, if so configured.
|
|
//
|
|
if (ERROR_SUCCESS == status && UMRDPDR_fSetClientPrinterDefault() && bSetDefault) {
|
|
|
|
DWORD statusSave = ERROR_SUCCESS;
|
|
BOOL fImpersonated = FALSE;
|
|
SaveDefaultPrinterContext(printerNameBuf);
|
|
|
|
//
|
|
//impersonate before setting the default printer as the api
|
|
//accesses hkcu. If the impersonation fails, the api will fail
|
|
//and we will log an error. But, before logging an error, we will
|
|
//need to revert to self.
|
|
//
|
|
if (!(fImpersonated = ImpersonateLoggedOnUser(hTokenForLoggedOnUser))) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:ImpersonateLoggedOnUser failed. Error:%ld.\n", GetLastError()));
|
|
}
|
|
|
|
if (!SetDefaultPrinter(printerNameBuf)) {
|
|
statusSave = GetLastError();
|
|
}
|
|
|
|
//
|
|
//if revert to self fails, consider it fatal
|
|
//
|
|
if (fImpersonated && !RevertToSelf()) {
|
|
status = GetLastError();
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:RevertToSelf failed. Error:%ld.\n", status));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (statusSave != ERROR_SUCCESS) {
|
|
WCHAR * param = printerNameBuf;
|
|
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: SetDefaultPrinter failed. Error: %ld\n",
|
|
statusSave));
|
|
|
|
TsLogError(EVENT_NOTIFY_SETDEFAULTPRINTER_FAILED,
|
|
EVENTLOG_ERROR_TYPE,
|
|
1,
|
|
¶m,
|
|
__LINE__);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
status = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Restore cached data for the new printer.
|
|
//
|
|
if (ERROR_SUCCESS == status && cacheDataLen) {
|
|
|
|
status = SetPrinterConfigInfo(
|
|
printerNameBuf,
|
|
cacheData,
|
|
cacheDataLen
|
|
);
|
|
|
|
if (status != ERROR_SUCCESS) {
|
|
|
|
WCHAR * param = printerNameBuf;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SetPrinterConfigInfo failed: %ld.\n", status));
|
|
|
|
SetLastError(status);
|
|
TsLogError(EVENT_NOTIFY_RESTORE_PRINTER_CONFIG_FAILED,
|
|
EVENTLOG_WARNING_TYPE,
|
|
1,
|
|
¶m,
|
|
__LINE__);
|
|
|
|
//
|
|
// We will go ahead and leave the queue created, but assume the config
|
|
// settings are bad on the client and cause them to be overwritten with
|
|
// the default config settings.
|
|
//
|
|
status = ERROR_SUCCESS;
|
|
triggerConfigChangeEvent = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
status = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the printer to make some modifications
|
|
//
|
|
if (queueCreated && (status == ERROR_SUCCESS)) {
|
|
|
|
ASSERT(hPrinter == INVALID_HANDLE_VALUE);
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:InstallPrinterWithPortName installing printer queue succeeded.\n"));
|
|
if( OpenPrinter(printerNameBuf, &hPrinter, &defaults) == FALSE ) {
|
|
hPrinter = INVALID_HANDLE_VALUE;
|
|
status = GetLastError();
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:OpenPrinter() %ws failed with %ld.\n", printerNameBuf, status));
|
|
}
|
|
|
|
//
|
|
// Add the session number to the printer queue data.
|
|
//
|
|
if (ERROR_SUCCESS == status) {
|
|
status = AddSessionIDToPrinterQueue(hPrinter, GETTHESESSIONID());
|
|
if( ERROR_SUCCESS != status ) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:AddSessionIDToPrinterQueue failed for %ws.\n",
|
|
printerNameBuf)
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add the different names to the printer queue data.
|
|
// They will be used if we need to redirect (again!) the printer.
|
|
//
|
|
if (ERROR_SUCCESS == status) {
|
|
status = AddNamesToPrinterQueue(hPrinter, &printerNames);
|
|
if (status != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:AddNamesToPrinterQueue failed for %ws with status %08x.\n",
|
|
printerNameBuf, status)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if (ShutdownFlag) {
|
|
status = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Check to make sure the printer doesn't already exist in the device list
|
|
//
|
|
printerNameExists =
|
|
(DRDEVLST_FindByServerDeviceName(DeviceList, printerNameBuf,
|
|
&ofs)
|
|
&& (DeviceList->devices[ofs].deviceType ==
|
|
RDPDR_DTYP_PRINT));
|
|
|
|
if (!printerNameExists) {
|
|
//
|
|
// Add the printer to the list of installed devices.
|
|
//
|
|
if (ERROR_SUCCESS == status) {
|
|
if( !DRDEVLST_Add(DeviceList, deviceID,
|
|
UMRDPDR_INVALIDSERVERDEVICEID,
|
|
RDPDR_DTYP_PRINT,
|
|
printerNameBuf,
|
|
printerName,
|
|
"UNKNOWN") ) {
|
|
|
|
// DRDEVLST_Add
|
|
status = ERROR_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Trigger a config change event if the install function
|
|
// indicated that we need to push config data out to the client.
|
|
//
|
|
if (triggerConfigChangeEvent && (status == ERROR_SUCCESS)) {
|
|
|
|
DRDEVLST_FindByClientDeviceID(DeviceList, deviceID, &ofs);
|
|
DeviceList->devices[ofs].fConfigInfoChanged = TRUE;
|
|
TriggerConfigChangeTimer();
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Close the printer handle if it was left open.
|
|
//
|
|
if (hPrinter != INVALID_HANDLE_VALUE) {
|
|
ClosePrinter(hPrinter);
|
|
}
|
|
|
|
//
|
|
// Delete the queue on failure.
|
|
//
|
|
if (status != ERROR_SUCCESS && queueCreated) {
|
|
UMRDPPRN_DeleteNamedPrinterQueue(printerNameBuf);
|
|
}
|
|
|
|
SetLastError(status);
|
|
|
|
return (status == ERROR_SUCCESS);
|
|
}
|
|
|
|
DWORD
|
|
AddSessionIDToPrinterQueue(
|
|
IN HANDLE hPrinter,
|
|
IN DWORD sessionID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add the session ID to printer queue associated with the specified
|
|
handle.
|
|
|
|
Arguments:
|
|
|
|
hPrinter - Handle for printer returned by OpenPrinter.
|
|
sessionID - Session ID.
|
|
|
|
Return Value:
|
|
|
|
Returns ERROR_SUCCESS on success. Error code, otherwise.
|
|
|
|
--*/
|
|
{
|
|
DWORD result;
|
|
|
|
result = SetPrinterData(
|
|
hPrinter, DEVICERDR_SESSIONID, REG_DWORD,
|
|
(PBYTE)&sessionID, sizeof(sessionID)
|
|
);
|
|
if (result != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:SetPrinterData failed with status %08x.\n", result));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DWORD
|
|
AddNamesToPrinterQueue(
|
|
IN HANDLE hPrinter,
|
|
IN PTS_PRINTER_NAMES pPrinterNames
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add the Server\Client\Printer names to printer queue
|
|
associated with the specified handle.
|
|
|
|
Arguments:
|
|
|
|
hPrinter - Handle for printer returned by OpenPrinter.
|
|
pPrinterNames - struct conataining the names.
|
|
|
|
Return Value:
|
|
|
|
Returns ERROR_SUCCESS on success. Error code, otherwise.
|
|
|
|
--*/
|
|
{
|
|
DWORD result = ERROR_SUCCESS;
|
|
|
|
if(pPrinterNames->pszServer) {
|
|
result = SetPrinterData(
|
|
hPrinter, DEVICERDR_PRINT_SERVER_NAME, REG_SZ,
|
|
(PBYTE)pPrinterNames->pszServer, (wcslen(pPrinterNames->pszServer) + 1)*sizeof(WCHAR)
|
|
);
|
|
if (result != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:SetPrinterData failed to set %ws with status %08x.\n",
|
|
pPrinterNames->pszServer, result));
|
|
}
|
|
}
|
|
if((result == ERROR_SUCCESS) && pPrinterNames->pszClient) {
|
|
result = SetPrinterData(
|
|
hPrinter, DEVICERDR_CLIENT_NAME, REG_SZ,
|
|
(PBYTE)pPrinterNames->pszClient, (wcslen(pPrinterNames->pszClient) + 1)*sizeof(WCHAR)
|
|
);
|
|
if (result != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:SetPrinterData failed to set %ws with status %08x.\n",
|
|
pPrinterNames->pszClient, result));
|
|
}
|
|
}
|
|
if((result == ERROR_SUCCESS) && pPrinterNames->pszPrinter) {
|
|
result = SetPrinterData(
|
|
hPrinter, DEVICERDR_PRINTER_NAME, REG_SZ,
|
|
(PBYTE)pPrinterNames->pszPrinter, (wcslen(pPrinterNames->pszPrinter) + 1)*sizeof(WCHAR)
|
|
);
|
|
if (result != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN:SetPrinterData failed to set %ws with status %08x.\n",
|
|
pPrinterNames->pszPrinter, result));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
UMRDPPRN_DeleteNamedPrinterQueue(
|
|
IN PWSTR printerName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Delete the named printer. This function does not remove the printer
|
|
from the comprehensive device management list.
|
|
|
|
Arguments:
|
|
|
|
printerName - Name of printer to delete.
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
HANDLE hPrinter;
|
|
PRINTER_DEFAULTS defaults = {NULL, NULL, PRINTER_ALL_ACCESS};
|
|
DWORD ofs;
|
|
BOOL result;
|
|
|
|
WCHAR defaultPrinterNameBuffer[MAX_PATH+1];
|
|
DWORD bufSize = sizeof(defaultPrinterNameBuffer) / sizeof(defaultPrinterNameBuffer[0]);
|
|
|
|
BOOL fPrinterIsDefault;
|
|
BOOL fDefaultPrinterSet = FALSE;
|
|
BOOL fImpersonated = FALSE;
|
|
|
|
defaultPrinterNameBuffer[0] = L'\0';
|
|
|
|
if (!PrintingModuleInitialized) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If the printer is one of the devices we are tracking, then
|
|
// we need to remove the notification object associated with the
|
|
// printer.
|
|
//
|
|
if (DRDEVLST_FindByServerDeviceName(DeviceList, printerName, &ofs) &&
|
|
(DeviceList->devices[ofs].deviceSpecificData != NULL)) {
|
|
|
|
PPRINTNOTIFYREC notifyRec = (PPRINTNOTIFYREC)
|
|
DeviceList->devices[ofs].deviceSpecificData;
|
|
ASSERT(notifyRec->notificationObject != NULL);
|
|
ASSERT(notifyRec->printerHandle != NULL);
|
|
|
|
WTBLOBJ_RemoveWaitableObject(
|
|
UMRDPPRN_WaitableObjMgr,
|
|
notifyRec->notificationObject
|
|
);
|
|
|
|
FindClosePrinterChangeNotification(
|
|
notifyRec->notificationObject
|
|
);
|
|
|
|
ClosePrinter(notifyRec->printerHandle);
|
|
|
|
FREEMEM(notifyRec);
|
|
|
|
DeviceList->devices[ofs].deviceSpecificData = NULL;
|
|
}
|
|
|
|
//
|
|
// Check to see if the printer we are deleting is a default printer
|
|
//
|
|
|
|
if (!(fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser))) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:ImpersonateLoggedOnUser failed. Error:%ld.\n", GetLastError()));
|
|
}
|
|
|
|
|
|
fPrinterIsDefault = (GetDefaultPrinter(defaultPrinterNameBuffer, &bufSize) &&
|
|
(wcscmp(defaultPrinterNameBuffer, printerName) == 0));
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:DeleteNamedPrinter deleting %ws.\n",
|
|
printerName));
|
|
|
|
if (fImpersonated&& !RevertToSelf()) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:RevertToSelf failed. Error:%ld.\n", GetLastError()));
|
|
}
|
|
|
|
|
|
//
|
|
// Open the printer.
|
|
//
|
|
result = OpenPrinter(printerName, &hPrinter, &defaults);
|
|
|
|
//
|
|
// Purge and delete the printer.
|
|
//
|
|
if (result) {
|
|
|
|
result = SetPrinter(hPrinter, 0, NULL, PRINTER_CONTROL_PURGE) &&
|
|
DeletePrinter(hPrinter);
|
|
|
|
}
|
|
else {
|
|
hPrinter = NULL;
|
|
}
|
|
|
|
//
|
|
// If the printer is the default, then restore the previously stored
|
|
// printer context.
|
|
//
|
|
if (fPrinterIsDefault) {
|
|
RestoreDefaultPrinterContext();
|
|
}
|
|
|
|
//
|
|
// Log an event on failure.
|
|
//
|
|
if (!result) {
|
|
WCHAR * param = printerName;
|
|
TsLogError(EVENT_NOTIFY_DELETE_PRINTER_FAILED,
|
|
EVENTLOG_ERROR_TYPE,
|
|
1,
|
|
¶m,
|
|
__LINE__);
|
|
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN:Unable to delete redirected printer - %ws. Error: %ld\n",
|
|
printerName, GetLastError()));
|
|
}
|
|
else
|
|
{
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:Printer successfully deleted.\n"));
|
|
}
|
|
|
|
//
|
|
// Close the printer if we successfully opened it.
|
|
//
|
|
if (hPrinter != NULL) {
|
|
ClosePrinter(hPrinter);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
SetDefaultPrinterToFirstFound(
|
|
BOOL impersonate
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enumerate all printers visible to the user and try to set the
|
|
first one we "can" to default.
|
|
|
|
Arguments:
|
|
|
|
impersonate - TRUE if this function should impersonate the user
|
|
prior to setting the default printer.
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
BOOL fImpersonated = FALSE;
|
|
PRINTER_INFO_4 * pPrinterInfo = NULL;
|
|
DWORD cbBuf = 0;
|
|
DWORD cReturnedStructs = 0;
|
|
DWORD i;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:SetDefaultPrinterToFirstFound Entered.\n"));
|
|
|
|
if (impersonate) {
|
|
|
|
//
|
|
// Impersonate Client
|
|
//
|
|
|
|
if ((UMRPDPPRN_TokenForLoggedOnUser == INVALID_HANDLE_VALUE) ||
|
|
!(fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser))) {
|
|
|
|
if (UMRPDPPRN_TokenForLoggedOnUser == INVALID_HANDLE_VALUE) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:UMRPDPPRN_TokenForLoggedOnUser is INVALID_HANDLE_VALUE.\n"));
|
|
}
|
|
else {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:ImpersonateLoggedOnUser failed. Error:%ld.\n", GetLastError()));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Enumerate Printers
|
|
//
|
|
|
|
if (!EnumPrinters(
|
|
PRINTER_ENUM_LOCAL, // Flags
|
|
NULL, // Name
|
|
4, // Print Info Type
|
|
(PBYTE)pPrinterInfo, // buffer
|
|
0, // Size of buffer
|
|
&cbBuf, // Required
|
|
&cReturnedStructs)) {
|
|
|
|
if(GetLastError() != ERROR_INSUFFICIENT_BUFFER ) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: EnumPrinters failed. Error: %ld.\n", GetLastError()));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (cbBuf == 0) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
pPrinterInfo = (PRINTER_INFO_4 *)ALLOCMEM(cbBuf);
|
|
|
|
if (pPrinterInfo == NULL) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: ALLOCMEM failed. Error: %ld.\n", GetLastError()));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!EnumPrinters(
|
|
PRINTER_ENUM_LOCAL,
|
|
NULL,
|
|
4,
|
|
(PBYTE)pPrinterInfo,
|
|
cbBuf,
|
|
&cbBuf,
|
|
&cReturnedStructs)) {
|
|
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: EnumPrinters failed. Error: %ld.\n", GetLastError()));
|
|
goto Cleanup;
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: Found %ld Local printers on Session %ld.\n",
|
|
cReturnedStructs, GETTHESESSIONID()));
|
|
|
|
if (fImpersonated) {
|
|
RevertToSelf();
|
|
fImpersonated = FALSE;
|
|
}
|
|
|
|
//
|
|
// Try to set one of the available printers as the default printer
|
|
//
|
|
|
|
for (i = 0; i < cReturnedStructs; i++) {
|
|
|
|
if (pPrinterInfo[i].pPrinterName) {
|
|
|
|
DWORD status = ERROR_SUCCESS;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: EnumPrinters - #%ld; Printer Name - %ws.\n",
|
|
i, pPrinterInfo[i].pPrinterName));
|
|
|
|
//
|
|
//impersonate before setting the default printer as the api
|
|
//accesses hkcu. If the impersonation fails, the api will fail
|
|
//and we will log an error. But, before logging an error, we will
|
|
//need to revert to self.
|
|
//
|
|
|
|
if (!(fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser))) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:ImpersonateLoggedOnUser failed. Error:%ld.\n", GetLastError()));
|
|
}
|
|
|
|
if (!SetDefaultPrinter(pPrinterInfo[i].pPrinterName)) {
|
|
//
|
|
//save the last error
|
|
//
|
|
status = GetLastError();
|
|
}
|
|
|
|
//
|
|
//if revert to self fails, consider it fatal
|
|
//
|
|
|
|
if (fImpersonated && !RevertToSelf()) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:RevertToSelf failed. Error:%ld.\n", GetLastError()));
|
|
fSuccess = FALSE;
|
|
break;
|
|
}
|
|
|
|
fImpersonated = FALSE;
|
|
|
|
if (status == ERROR_SUCCESS) {
|
|
fSuccess = TRUE;
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: The printer %ws was set as the Default Printer.\n",
|
|
pPrinterInfo[i].pPrinterName));
|
|
|
|
break;
|
|
}
|
|
else {
|
|
WCHAR * param = pPrinterInfo[i].pPrinterName;
|
|
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: SetDefaultPrinter failed. Error: %ld\n",
|
|
status));
|
|
|
|
TsLogError(EVENT_NOTIFY_SETDEFAULTPRINTER_FAILED,
|
|
EVENTLOG_ERROR_TYPE,
|
|
1,
|
|
¶m,
|
|
__LINE__);
|
|
}
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if (fImpersonated) {
|
|
RevertToSelf();
|
|
}
|
|
|
|
if (pPrinterInfo != NULL) {
|
|
FREEMEM(pPrinterInfo);
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:SetDefaultPrinterToFirstFound Leaving. fSuccess is %d.\n", fSuccess));
|
|
return fSuccess;
|
|
}
|
|
|
|
BOOL
|
|
HandlePrinterConfigChangeNotification(
|
|
IN DWORD serverDeviceID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle notification from the spooler that the config info of a printer has changed.
|
|
|
|
Arguments:
|
|
|
|
serverDeviceID - Server-assigned device ID associated with the printer
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE.
|
|
|
|
--*/
|
|
{
|
|
DWORD ofs;
|
|
time_t timeDelta;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:HandlePrinterConfigChangeNotification entered.\n"));
|
|
//
|
|
// If this is for one of our printers.
|
|
//
|
|
if (DRDEVLST_FindByServerDeviceID(DeviceList,
|
|
serverDeviceID, &ofs)) {
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:Config Info for Printer %ws has changed.\n",
|
|
DeviceList->devices[ofs].serverDeviceName));
|
|
|
|
//
|
|
// The install time for the device needs to be beyond a configurable threshold
|
|
// before we will do anything about the change. This eliminates forwarding
|
|
// unnecessary (non-user initiated) configuration changes to the client.
|
|
//
|
|
timeDelta = time(NULL) - DeviceList->devices[ofs].installTime;
|
|
if ((DWORD)timeDelta > ConfigSendThreshold) {
|
|
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:Processing config change because outside change time delta.\n")
|
|
);
|
|
|
|
//
|
|
// Need to record that the configuration has changed and set a
|
|
// timer on forwarding to the client in order to compress changes into
|
|
// a single message to the client.
|
|
//
|
|
DeviceList->devices[ofs].fConfigInfoChanged = TRUE;
|
|
TriggerConfigChangeTimer();
|
|
}
|
|
else {
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:Skipping config change because inside change time delta.\n")
|
|
);
|
|
}
|
|
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
SendPrinterConfigInfoToClient(
|
|
IN PCWSTR printerName,
|
|
IN LPBYTE pConfigInfo,
|
|
IN DWORD ConfigInfoSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send a printer update cache data message to the client.
|
|
|
|
Arguments:
|
|
|
|
printerName - Name of printer.
|
|
pConfigInfo - Configuration Information.
|
|
ConfigInfoSize - size of config info.
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
PRDPDR_PRINTER_CACHEDATA_PACKET cachedDataPacket;
|
|
DWORD cachedDataPacketSize;
|
|
PRDPDR_PRINTER_UPDATE_CACHEDATA cachedData;
|
|
BOOL result;
|
|
DWORD printerSz;
|
|
PWSTR str;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: SendPrinterConfigInfoToClient entered.\n"));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SendPrinterConfigInfoToClient printer name is %ws.\n",
|
|
printerName));
|
|
|
|
//
|
|
// Calculate the message size.
|
|
//
|
|
printerSz = ((wcslen(printerName) + 1) * sizeof(WCHAR));
|
|
cachedDataPacketSize = sizeof(RDPDR_PRINTER_CACHEDATA_PACKET) +
|
|
sizeof(RDPDR_PRINTER_UPDATE_CACHEDATA) +
|
|
printerSz +
|
|
ConfigInfoSize;
|
|
|
|
//
|
|
// Allocate the message.
|
|
//
|
|
cachedDataPacket = (PRDPDR_PRINTER_CACHEDATA_PACKET)ALLOCMEM(
|
|
cachedDataPacketSize
|
|
);
|
|
result = (cachedDataPacket != NULL);
|
|
if (result) {
|
|
|
|
PBYTE pData = NULL;
|
|
//
|
|
// Set up the packet.
|
|
//
|
|
cachedDataPacket->Header.PacketId = DR_PRN_CACHE_DATA;
|
|
cachedDataPacket->Header.Component = RDPDR_CTYP_PRN;
|
|
cachedDataPacket->EventId = RDPDR_UPDATE_PRINTER_EVENT;
|
|
|
|
//
|
|
// Set up the cached data.
|
|
//
|
|
cachedData = (PRDPDR_PRINTER_UPDATE_CACHEDATA)(
|
|
(PBYTE)cachedDataPacket +
|
|
sizeof(RDPDR_PRINTER_CACHEDATA_PACKET)
|
|
);
|
|
cachedData->PrinterNameLen = printerSz;
|
|
cachedData->ConfigDataLen = ConfigInfoSize;
|
|
|
|
//
|
|
// Add the printer name.
|
|
//
|
|
str = (PWSTR)((PBYTE)cachedData + sizeof(RDPDR_PRINTER_UPDATE_CACHEDATA));
|
|
wcscpy(str, printerName);
|
|
|
|
//
|
|
// Add the config info.
|
|
//
|
|
pData = (PBYTE)str + printerSz;
|
|
memcpy(pData, pConfigInfo, ConfigInfoSize);
|
|
|
|
//
|
|
// Send the message to the client.
|
|
//
|
|
result = UMRDPDR_SendMessageToClient(
|
|
cachedDataPacket,
|
|
cachedDataPacketSize
|
|
);
|
|
|
|
// Release the buffer.
|
|
FREEMEM(cachedDataPacket);
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: Can't allocate cached data packet.\n"));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DWORD
|
|
GetPrinterConfigInfo(
|
|
LPCWSTR printerName,
|
|
LPBYTE * ppBuffer,
|
|
LPDWORD pdwBufSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the Printer configuration Information from PrintUI.
|
|
|
|
Arguments:
|
|
|
|
printerName - Name of the printer.
|
|
ppBuffer - A place holder for a buffer pointer.
|
|
This functions allocates memory and sends it out through this argument
|
|
The caller should free this memory.
|
|
pdwBufSize - size of allocated memory.
|
|
|
|
Return Value:
|
|
|
|
Returns ERROR_SUCCESS if successful.
|
|
|
|
--*/
|
|
{
|
|
WCHAR fileName[MAX_PATH];
|
|
WCHAR tempPath[MAX_PATH];
|
|
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
|
|
DWORD dwResult;
|
|
DWORD dwBytes;
|
|
DWORD dwBytesRead;
|
|
BOOL fImpersonated = FALSE;
|
|
|
|
ASSERT(ppBuffer && pdwBufSize);
|
|
*pdwBufSize = 0;
|
|
|
|
//
|
|
// Get Temp Folder
|
|
// Impersonate first, so the file has the proper acls on it
|
|
// Ignore the error, the worst case is caching will not be possible, as we create it with system acls
|
|
// No security hole here
|
|
//
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:GetPrinterConfigInfo entered.\n"));
|
|
|
|
fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser);
|
|
|
|
dwResult = GetTempPathW(MAX_PATH, tempPath);
|
|
if (dwResult > 0 && dwResult <= MAX_PATH) {
|
|
GetTempFileNameW(tempPath, TEMP_FILE_PREFIX, 0, fileName);
|
|
}
|
|
else {
|
|
GetTempFileNameW(L".", TEMP_FILE_PREFIX, 0, fileName);
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:Temp File Name is %ws.\n", fileName));
|
|
|
|
//
|
|
// While impersonating the logged on user, fetch the settings.
|
|
//
|
|
if (fImpersonated) {
|
|
|
|
dwResult = CallPrintUiPersistFunc(printerName, fileName,
|
|
CMDLINE_FOR_STORING_CONFIGINFO_IMPERSONATE);
|
|
RevertToSelf();
|
|
|
|
if (dwResult != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:CallPrintUiPersistFunc failed with code: %ld\n", dwResult));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
else {
|
|
dwResult = GetLastError();
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:ImpersonateLoggedOnUser: %ld\n", dwResult));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the file and read the contents to the buffer
|
|
//
|
|
|
|
hFile = CreateFileW(
|
|
fileName,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE) {
|
|
dwResult = GetLastError();
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:CreateFileW failed with code: %ld\n", dwResult));
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwBytes = GetFileSize(hFile, NULL);
|
|
|
|
*ppBuffer = (LPBYTE) ALLOCMEM(dwBytes);
|
|
if (*ppBuffer == NULL) {
|
|
dwResult = GetLastError();
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:AllocMem failed with code: %ld\n", dwResult));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!ReadFile(
|
|
hFile,
|
|
*ppBuffer,
|
|
dwBytes,
|
|
&dwBytesRead,
|
|
NULL
|
|
)) {
|
|
dwResult = GetLastError();
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:ReadFile failed with code: %ld\n", dwResult));
|
|
goto Cleanup;
|
|
}
|
|
|
|
* pdwBufSize = dwBytesRead;
|
|
dwResult = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Close the file and delete it
|
|
//
|
|
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(hFile);
|
|
|
|
DeleteFileW(fileName);
|
|
}
|
|
|
|
return dwResult;
|
|
|
|
}
|
|
DWORD
|
|
SetPrinterConfigInfo(
|
|
LPCWSTR printerName,
|
|
LPVOID lpBuffer,
|
|
DWORD dwBufSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the Printer configuration Information from the cache data.
|
|
|
|
Arguments:
|
|
|
|
printerName - Name of the printer.
|
|
lpBuffer - Cache data.
|
|
pdwBufSize - Size of Cache data.
|
|
|
|
Return Value:
|
|
|
|
Returns ERROR_SUCCESS if successful.
|
|
|
|
--*/
|
|
{
|
|
WCHAR fileName[MAX_PATH] = L"";
|
|
WCHAR tempPath[MAX_PATH] = L"";
|
|
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
|
|
DWORD dwResult;
|
|
DWORD dwBytes;
|
|
BOOL fImpersonated = FALSE;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SetPrinterConfigInfo entered.\n"));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:printerName is %ws.\n", printerName));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:bufsize is %ld.\n", dwBufSize));
|
|
|
|
//
|
|
// Get Temp Folder
|
|
// Impersonate first, so the file has the proper acls on it
|
|
// Ignore the error, the worst case is caching will not be possible, as we create it with system acls
|
|
// No security hole here
|
|
//
|
|
fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser);
|
|
|
|
if (!fImpersonated) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SetPrinterConfigInfo Impersonation failed. Creating temp file in the context of system\n"));
|
|
}
|
|
|
|
dwResult = GetTempPathW(MAX_PATH, tempPath);
|
|
if (dwResult > 0 && dwResult <= MAX_PATH) {
|
|
dwResult = GetTempFileNameW(tempPath, TEMP_FILE_PREFIX, 0, fileName);
|
|
}
|
|
else {
|
|
dwResult = GetTempFileNameW(L".", TEMP_FILE_PREFIX, 0, fileName);
|
|
}
|
|
|
|
if( dwResult == 0 ) {
|
|
dwResult = GetLastError();
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:GetTempFileNameW failed with code: %ld\n", dwResult));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Save the contents to the file
|
|
//
|
|
|
|
hFile = CreateFileW(
|
|
fileName,
|
|
GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE) {
|
|
dwResult = GetLastError();
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:CreateFileW failed with code: %ld\n", dwResult));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ((!WriteFile(
|
|
hFile,
|
|
lpBuffer,
|
|
dwBufSize,
|
|
&dwBytes,
|
|
NULL
|
|
)) ||
|
|
(dwBytes < dwBufSize)) {
|
|
dwResult = GetLastError();
|
|
CloseHandle(hFile);
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:WriteFile failed with code: %ld\n", dwResult));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(hFile);
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:fileName is %ws.\n", fileName));
|
|
|
|
if (fImpersonated) {
|
|
fImpersonated = !(RevertToSelf());
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:RevertToSelf %s\n", fImpersonated?"Failed":"Passed"));
|
|
}
|
|
|
|
//
|
|
// Call printui the first time as system. We do this twice because
|
|
// some settings require that we be running as system vs. user.
|
|
//
|
|
dwResult = CallPrintUiPersistFunc(printerName, fileName,
|
|
CMDLINE_FOR_RESTORING_CONFIGINFO_NOIMPERSONATE);
|
|
if (dwResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Call printui the second time as the logged on user.
|
|
//
|
|
if (ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser)) {
|
|
dwResult = CallPrintUiPersistFunc(printerName, fileName,
|
|
CMDLINE_FOR_RESTORING_CONFIGINFO_IMPERSONATE);
|
|
RevertToSelf();
|
|
if (dwResult != ERROR_SUCCESS) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
else {
|
|
dwResult = GetLastError();
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: ImpersonateLoggedOnUser: %08X\n",
|
|
dwResult));
|
|
}
|
|
|
|
Cleanup:
|
|
if (fImpersonated) {
|
|
RevertToSelf();
|
|
}
|
|
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
DeleteFileW(fileName);
|
|
}
|
|
|
|
return dwResult;
|
|
}
|
|
|
|
DWORD
|
|
CallPrintUiPersistFunc(
|
|
LPCWSTR printerName,
|
|
LPCWSTR fileName,
|
|
LPCWSTR formatString
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Calls the PrintUI function for storing or restoring printer config info.
|
|
|
|
Arguments:
|
|
|
|
printerName - Name of the printer.
|
|
fileName - Name of the temp file.
|
|
formatString - PrintUI save/restore format string.
|
|
|
|
Return Value:
|
|
|
|
Returns ERROR_SUCCESS if successful.
|
|
|
|
--*/
|
|
{
|
|
WCHAR cmdLine[3 * MAX_PATH + (sizeof(CMDLINE_FOR_RESTORING_CONFIGINFO_NOIMPERSONATE)/sizeof(WCHAR)) + 2];
|
|
WCHAR formattedPrinterName[(MAX_PATH+1)*2];
|
|
WCHAR * pSource, * pDest;
|
|
|
|
DWORD dwResult = ERROR_SUCCESS;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:CallPrintUiPersistFunc Entered.\n"));
|
|
|
|
ASSERT(PrintUIEntryFunc != NULL);
|
|
ASSERT(printerName != NULL);
|
|
ASSERT(fileName != NULL);
|
|
|
|
//
|
|
// Format the printer name
|
|
//
|
|
|
|
pSource = (WCHAR *)printerName;
|
|
pDest = formattedPrinterName;
|
|
|
|
while (*pSource) {
|
|
if (*pSource == L'\"' || *pSource == L'@') {
|
|
*pDest++ = L'\\';
|
|
}
|
|
*pDest++ = *pSource++;
|
|
//
|
|
// pDest may have buffer overflow. Check for it.
|
|
//
|
|
if ((pDest - formattedPrinterName) >=
|
|
(sizeof(formattedPrinterName)/sizeof(formattedPrinterName[0]) - 1)) {
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
}
|
|
*pDest = L'\0';
|
|
|
|
//
|
|
// Format the command line to be passed to PrintUI function
|
|
//
|
|
|
|
swprintf(cmdLine, formatString, formattedPrinterName, fileName);
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:cmdLine is: %ws\n", cmdLine));
|
|
|
|
dwResult = (DWORD)PrintUIEntryFunc(
|
|
NULL, // Window handle
|
|
PrintUILibHndl, // Handle to DLL instance.
|
|
cmdLine, // Command Line
|
|
TRUE
|
|
);
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:PrintUiEntryFunc returned: %ld\n", dwResult));
|
|
|
|
return dwResult;
|
|
}
|
|
|
|
void
|
|
WaitableTimerSignaled(
|
|
HANDLE waitableObject,
|
|
PVOID clientData
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Goes through the device list checking if the config info has changed
|
|
for any of the printers. If so, sends the config info to the client.
|
|
|
|
Arguments:
|
|
|
|
waitableObject - Associated waitable object.
|
|
clientData - Ignored.
|
|
|
|
Return Value:
|
|
|
|
NONE.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwResult = ERROR_SUCCESS;
|
|
DWORD i;
|
|
BOOL fImpersonated;
|
|
LARGE_INTEGER li;
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: WaitableTimerSignaled Entered.\n"));
|
|
|
|
//
|
|
// Compute as large number as possible, but do not overflow.
|
|
//
|
|
li.QuadPart = Int32x32To64(INFINITE_WAIT_PERIOD, INFINITE_WAIT_PERIOD);
|
|
li.QuadPart *= -1; //relative time
|
|
//
|
|
// Reset the waitable timer to a non-signaled state.
|
|
// We don't want it signaled until we do a TriggerConfigChangeTimer.
|
|
// So, set the time interval to a very large number.
|
|
//
|
|
ASSERT(g_fTimerSet);
|
|
ASSERT(waitableObject == WaitableTimer);
|
|
|
|
if (!SetWaitableTimer(waitableObject,
|
|
&li,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
FALSE)) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SetWaitableTimer Failed."
|
|
"Error: %ld.\n", GetLastError()));
|
|
}
|
|
//
|
|
// Now, kill the timer
|
|
// TODO: Do we really need this?
|
|
//
|
|
if (!CancelWaitableTimer(waitableObject)) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: CancelWaitableTimer failed."
|
|
"Error: %ld\n", GetLastError()));
|
|
}
|
|
g_fTimerSet = FALSE;
|
|
//
|
|
// Iterate the Devices List to see if config Info has changed for any
|
|
//
|
|
|
|
for (i = 0; i < DeviceList->deviceCount; i++) {
|
|
if (DeviceList->devices[i].fConfigInfoChanged) {
|
|
|
|
LPBYTE pConfigData = NULL;
|
|
DWORD size = 0;
|
|
|
|
//
|
|
// reset error code in order to process next item
|
|
//
|
|
dwResult = ERROR_SUCCESS;
|
|
|
|
// Reset the flag
|
|
DeviceList->devices[i].fConfigInfoChanged = FALSE;
|
|
|
|
DBGMSG(DBG_INFO, ("UMRDPPRN: Trying to Get ConfigInfo for the printer %ws\n",
|
|
DeviceList->devices[i].serverDeviceName));
|
|
|
|
//
|
|
// Get the printer config info.
|
|
//
|
|
if (dwResult == ERROR_SUCCESS) {
|
|
dwResult = GetPrinterConfigInfo(
|
|
DeviceList->devices[i].serverDeviceName,
|
|
&pConfigData, &size);
|
|
}
|
|
|
|
if (dwResult == ERROR_SUCCESS) {
|
|
|
|
// Send this info to the client
|
|
SendPrinterConfigInfoToClient(
|
|
DeviceList->devices[i].clientDeviceName,
|
|
pConfigData,
|
|
size
|
|
);
|
|
}
|
|
|
|
if (pConfigData) {
|
|
FREEMEM(pConfigData);
|
|
}
|
|
}
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: Leaving WaitableTimerSignaled.\n"));
|
|
}
|
|
|
|
BOOL
|
|
SendPrinterRenameToClient(
|
|
IN PCWSTR oldprinterName,
|
|
IN PCWSTR newprinterName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send a printer update cache data message to the client.
|
|
|
|
Arguments:
|
|
|
|
oldprinterName - Old Name of printer.
|
|
newprinterName - New Name of printer.
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
PRDPDR_PRINTER_CACHEDATA_PACKET cachedDataPacket;
|
|
DWORD cachedDataPacketSize;
|
|
PRDPDR_PRINTER_RENAME_CACHEDATA cachedData;
|
|
BOOL result;
|
|
DWORD oldNameLen, newNameLen;
|
|
PWSTR str;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: SendPrinterRenameToClient entered.\n"));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SendPrinterRenameToClient Old printer name is %ws.\n",
|
|
oldprinterName));
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:SendPrinterRenameToClient New printer name is %ws.\n",
|
|
newprinterName));
|
|
|
|
//
|
|
// Calculate the message size.
|
|
//
|
|
oldNameLen = (oldprinterName) ? ((wcslen(oldprinterName) + 1) * sizeof(WCHAR)) : 0;
|
|
newNameLen = (newprinterName) ? ((wcslen(newprinterName) + 1) * sizeof(WCHAR)) : 0;
|
|
|
|
if (!(oldNameLen && newNameLen)) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: Printer name is empty. Returning FALSE\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
cachedDataPacketSize = sizeof(RDPDR_PRINTER_CACHEDATA_PACKET) +
|
|
sizeof(RDPDR_PRINTER_RENAME_CACHEDATA) +
|
|
oldNameLen + newNameLen;
|
|
|
|
//
|
|
// Allocate the message.
|
|
//
|
|
cachedDataPacket = (PRDPDR_PRINTER_CACHEDATA_PACKET)ALLOCMEM(
|
|
cachedDataPacketSize
|
|
);
|
|
result = (cachedDataPacket != NULL);
|
|
if (result) {
|
|
//
|
|
// Set up the packet.
|
|
//
|
|
cachedDataPacket->Header.PacketId = DR_PRN_CACHE_DATA;
|
|
cachedDataPacket->Header.Component = RDPDR_CTYP_PRN;
|
|
cachedDataPacket->EventId = RDPDR_RENAME_PRINTER_EVENT;
|
|
|
|
//
|
|
// Set up the cached data.
|
|
//
|
|
cachedData = (PRDPDR_PRINTER_RENAME_CACHEDATA)(
|
|
(PBYTE)cachedDataPacket +
|
|
sizeof(RDPDR_PRINTER_CACHEDATA_PACKET)
|
|
);
|
|
cachedData->OldPrinterNameLen = oldNameLen;
|
|
cachedData->NewPrinterNameLen = newNameLen;
|
|
|
|
//
|
|
// Add the printer names.
|
|
//
|
|
str = (PWSTR)((PBYTE)cachedData + sizeof(RDPDR_PRINTER_RENAME_CACHEDATA));
|
|
wcscpy(str, oldprinterName);
|
|
|
|
str = (PWSTR)((PBYTE)str + oldNameLen);
|
|
wcscpy(str, newprinterName);
|
|
|
|
//
|
|
// Send the message to the client.
|
|
//
|
|
result = UMRDPDR_SendMessageToClient(
|
|
cachedDataPacket,
|
|
cachedDataPacketSize
|
|
);
|
|
|
|
// Release the buffer.
|
|
FREEMEM(cachedDataPacket);
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: SendPrinterRenameToClient Leaving.\n"));
|
|
|
|
return result;
|
|
}
|
|
|
|
VOID LoadConfigurableValues()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Load configurable values out of the registry.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
LONG status;
|
|
HKEY regKey;
|
|
LONG sz;
|
|
BOOL fetchResult;
|
|
LONG s;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: LoadConfigurableValues entered.\n"));
|
|
|
|
//
|
|
// Open the top level reg key for configurable resources.
|
|
//
|
|
status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, CONFIGREGKEY, 0,
|
|
KEY_READ, ®Key);
|
|
|
|
//
|
|
// Read the configurable threshold for delta between printer installation
|
|
// time and forwarding of first user-initiated configuration change data
|
|
// to the client. The units for this value is in seconds.
|
|
//
|
|
if (status == ERROR_SUCCESS) {
|
|
sz = sizeof(ConfigSendThreshold);
|
|
s = RegQueryValueEx(regKey, CONFIGTHRESHOLDREGVALUE, NULL,
|
|
NULL, (PBYTE)&ConfigSendThreshold, &sz);
|
|
if (s != ERROR_SUCCESS) {
|
|
ConfigSendThreshold = CONFIGTHRESHOLDDEFAULT;
|
|
DBGMSG(DBG_WARN,
|
|
("UMRDPPRN: LoadConfigurableValues can't read config threshold: %ld.\n", s));
|
|
}
|
|
}
|
|
else {
|
|
regKey = NULL;
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE,
|
|
("UMRDPPRN:Config. change threshold is %ld.\n",
|
|
ConfigSendThreshold)
|
|
);
|
|
|
|
//
|
|
// Read the location of the user-configurable Client Driver Name Mapping INF.
|
|
//
|
|
ASSERT(UserDefinedMappingINFName == NULL);
|
|
ASSERT(UserDefinedMappingINFSection == NULL);
|
|
fetchResult = FALSE;
|
|
if (status == ERROR_SUCCESS) {
|
|
fetchResult = TSNUTL_FetchRegistryValue(
|
|
regKey,
|
|
CONFIGUSERDEFINEDMAPPINGINFNAMEVALUE,
|
|
(PBYTE *)&UserDefinedMappingINFName
|
|
);
|
|
}
|
|
|
|
//
|
|
// Read the section name of the user-configurable Client Driver Name Mapping INF.
|
|
//
|
|
if ((status == ERROR_SUCCESS) && fetchResult) {
|
|
fetchResult = TSNUTL_FetchRegistryValue(
|
|
regKey,
|
|
CONFIGUSERDEFINEDMAPPINGINFSECTIONVALUE,
|
|
(PBYTE *)&UserDefinedMappingINFSection
|
|
);
|
|
if (!fetchResult) {
|
|
ASSERT(UserDefinedMappingINFSection == NULL);
|
|
FREEMEM(UserDefinedMappingINFName);
|
|
UserDefinedMappingINFName = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Close the parent reg key.
|
|
//
|
|
if (regKey != NULL) {
|
|
RegCloseKey(regKey);
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: LoadConfigurableValues exiting.\n"));
|
|
}
|
|
|
|
HANDLE
|
|
RegisterForPrinterPrefNotify()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Register for changes to one of this session's printers' Printing
|
|
Preferences.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
Handle to an event that will be signaled when the printing preferences
|
|
change for one of this session's printers. NULL is returned on error.
|
|
|
|
--*/
|
|
{
|
|
LONG ret;
|
|
HANDLE hEvent;
|
|
BOOL impersonated=FALSE;
|
|
NTSTATUS status;
|
|
HANDLE hKeyCurrentUser=INVALID_HANDLE_VALUE;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: RegisterForPrinterPrefNotify entered.\n"));
|
|
|
|
//
|
|
// Open the event.
|
|
//
|
|
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
if (hEvent == NULL) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN: can't create event: %ld.\n",
|
|
GetLastError()));
|
|
}
|
|
|
|
//
|
|
// Need to impersonate the logged in user so we can get to the right
|
|
// dev mode.
|
|
//
|
|
if (hEvent != NULL) {
|
|
if ((UMRPDPPRN_TokenForLoggedOnUser == INVALID_HANDLE_VALUE) ||
|
|
!ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser)) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: can't impersonate user %ld.\n",
|
|
GetLastError()));
|
|
CloseHandle(hEvent);
|
|
hEvent = NULL;
|
|
impersonated = FALSE;
|
|
}
|
|
else {
|
|
impersonated = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Attempt to open the the HKEY_CURRENT_USER predefined handle.
|
|
//
|
|
if (hEvent != NULL) {
|
|
status = RtlOpenCurrentUser(KEY_ALL_ACCESS, &hKeyCurrentUser);
|
|
if (!NT_SUCCESS(status)) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: can't open HKCU: %08X.\n",status));
|
|
CloseHandle(hEvent);
|
|
hEvent = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Open the printing system dev mode for this user. If it doesn't exist,
|
|
// it will be created. This key is changed when a user modifies their printing
|
|
// preferences.
|
|
//
|
|
if (hEvent != NULL) {
|
|
ASSERT(DevModeHKey == INVALID_HANDLE_VALUE);
|
|
ret = RegCreateKeyEx(
|
|
hKeyCurrentUser, // handle to an open key
|
|
TEXT("Printers\\DevModePerUser"), // address of subkey name
|
|
0, // reserved
|
|
NULL, // address of class string
|
|
REG_OPTION_NON_VOLATILE, // special options flag
|
|
KEY_ALL_ACCESS, // desired security access
|
|
NULL, // key security structure
|
|
&DevModeHKey, // buffer for opened handle
|
|
NULL // disposition value buffer
|
|
);
|
|
|
|
if (ret != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN: can't open printing dev mode: %ld.\n", ret)
|
|
);
|
|
CloseHandle(hEvent);
|
|
hEvent = NULL;
|
|
DevModeHKey = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Revert back to the system user.
|
|
//
|
|
if (impersonated) {
|
|
RevertToSelf();
|
|
}
|
|
|
|
//
|
|
// Register for notification on this key.
|
|
//
|
|
if (hEvent != NULL) {
|
|
ret = RegNotifyChangeKeyValue(
|
|
DevModeHKey,
|
|
TRUE,
|
|
REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
|
|
hEvent,
|
|
TRUE
|
|
);
|
|
if (ret != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_ERROR,
|
|
("UMRDPPRN: can't register for registry key change event: %ld.\n",
|
|
ret));
|
|
CloseHandle(hEvent);
|
|
hEvent = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Close the handle to HKCU.
|
|
//
|
|
if (hKeyCurrentUser != INVALID_HANDLE_VALUE) {
|
|
RegCloseKey(hKeyCurrentUser);
|
|
}
|
|
|
|
//
|
|
// Log an event on error.
|
|
//
|
|
if (hEvent == NULL) {
|
|
TsLogError(
|
|
EVENT_NOTIFY_FAILEDTOREGFOR_SETTING_NOTIFY,
|
|
EVENTLOG_ERROR_TYPE,
|
|
0,
|
|
NULL,
|
|
__LINE__
|
|
);
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: RegisterForPrinterPrefNotify done.\n"));
|
|
|
|
return hEvent;
|
|
}
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check if a specific printer driver already installed.
|
|
|
|
Arguments:
|
|
|
|
clientDriver - Client Driver Name.
|
|
|
|
Return Values:
|
|
|
|
ERROR_SUCCESS if driver already installed.
|
|
ERROR_FILE_NOT_FOUND if driver is not installed.
|
|
or other error code
|
|
|
|
--*/
|
|
DWORD PrinterDriverInstalled(
|
|
IN PCWSTR clientDriver
|
|
)
|
|
{
|
|
PDRIVER_INFO_1 pDrivers = NULL;
|
|
DWORD cbNeeded;
|
|
DWORD cbAllocated;
|
|
DWORD cbReturned;
|
|
DWORD dwStatus = ERROR_FILE_NOT_FOUND;
|
|
BOOL bSuccess;
|
|
DWORD i;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: PrinterDriverInstalled looking for %ws.\n", clientDriver));
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if( ShutdownFlag ) {
|
|
return ERROR_SHUTDOWN_IN_PROGRESS;
|
|
}
|
|
|
|
bSuccess = EnumPrinterDrivers(
|
|
NULL,
|
|
NULL,
|
|
1, // we only need a list of driver name
|
|
(LPBYTE) pDrivers,
|
|
0,
|
|
&cbNeeded,
|
|
&cbReturned
|
|
);
|
|
|
|
if( TRUE == bSuccess || ( dwStatus = GetLastError() ) == ERROR_INSUFFICIENT_BUFFER ) {
|
|
|
|
//
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
if( ShutdownFlag ) {
|
|
dwStatus = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
}
|
|
else {
|
|
|
|
pDrivers = (PDRIVER_INFO_1)ALLOCMEM( cbAllocated = cbNeeded );
|
|
|
|
if( NULL != pDrivers ) {
|
|
bSuccess = EnumPrinterDrivers(
|
|
NULL,
|
|
NULL,
|
|
1, // we only need a list of driver name
|
|
(LPBYTE)pDrivers,
|
|
cbAllocated,
|
|
&cbNeeded,
|
|
&cbReturned
|
|
);
|
|
|
|
if( TRUE == bSuccess ) {
|
|
//
|
|
// loop thru entire list to find out if interested driver
|
|
// exists on local machine.
|
|
// Return immediately if the DLL is trying to shut down. This
|
|
// is to help prevent us from getting stuck in a system call.
|
|
//
|
|
dwStatus = ERROR_FILE_NOT_FOUND;
|
|
|
|
for( i=0; FALSE == ShutdownFlag && i < cbReturned; i++ ) {
|
|
if( 0 == _wcsicmp( pDrivers[i].pName, clientDriver ) ) {
|
|
dwStatus = ERROR_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( ShutdownFlag ) {
|
|
dwStatus = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
}
|
|
}
|
|
else {
|
|
dwStatus = GetLastError();
|
|
}
|
|
|
|
FREEMEM( pDrivers );
|
|
|
|
}
|
|
else {
|
|
dwStatus = GetLastError();
|
|
}
|
|
}
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: PrinterDriverInstalled done with %ld.\n", dwStatus));
|
|
|
|
return dwStatus;
|
|
}
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Map a client printer driver name to a server printer driver name,
|
|
if a mapping is defined in ntprint.inf or the inf that is available
|
|
to the end-user.
|
|
|
|
Arguments:
|
|
|
|
clientDriver - Client driver name.
|
|
mappedName - Pointer to mapped driver name buffer. Should
|
|
be released if the mapping is successful.
|
|
mappedNameBufSize - Returned size of mapped driver name buffer.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the client driver name was mapped.
|
|
|
|
--*/
|
|
BOOL MapClientPrintDriverName(
|
|
IN PCWSTR clientDriver,
|
|
IN OUT PWSTR *mappedName,
|
|
IN OUT DWORD *mappedNameBufSize
|
|
)
|
|
{
|
|
BOOL clientDriverMapped = FALSE;
|
|
ULONG requiredSize;
|
|
USHORT nNumPrintSections;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: MapClientPrintDriverName with %ws.\n", clientDriver));
|
|
|
|
|
|
//
|
|
// First, check the user-defined INF section if it is so configured.
|
|
//
|
|
if ((UserDefinedMappingINFName != NULL) &&
|
|
(UserDefinedMappingINFSection != NULL)) {
|
|
while (!(clientDriverMapped =
|
|
RDPDRUTL_MapPrintDriverName(
|
|
clientDriver, UserDefinedMappingINFName,
|
|
UserDefinedMappingINFSection, 0, 1,
|
|
*mappedName,
|
|
(*mappedNameBufSize) / sizeof(WCHAR),
|
|
&requiredSize
|
|
)) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
|
|
if (!UMRDPDR_ResizeBuffer(&MappedDriverNameBuf, requiredSize * sizeof(WCHAR),
|
|
&MappedDriverNameBufSize)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( clientDriverMapped ) {
|
|
goto Done;
|
|
}
|
|
|
|
//
|
|
// Client does not send over platform info (NT4, Win9x...) so try both
|
|
// upgrade file
|
|
//
|
|
|
|
//
|
|
// printupg.inf contain block driver and its mapping to inbox driver which
|
|
// is not in ntprint.inf.
|
|
//
|
|
|
|
nNumPrintSections = 0;
|
|
|
|
while( (NULL != prgwszPrinterSectionNames[nNumPrintSections]) &&
|
|
!(clientDriverMapped =
|
|
RDPDRUTL_MapPrintDriverName(
|
|
clientDriver,
|
|
L"printupg.inf",
|
|
prgwszPrinterSectionNames[nNumPrintSections++],
|
|
0,
|
|
1,
|
|
*mappedName,
|
|
(*mappedNameBufSize) / sizeof(WCHAR),
|
|
&requiredSize
|
|
))&&
|
|
(GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
|
|
if (!UMRDPDR_ResizeBuffer(&MappedDriverNameBuf, requiredSize * sizeof(WCHAR),
|
|
&MappedDriverNameBufSize)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
if( clientDriverMapped ) {
|
|
goto Done;
|
|
}
|
|
|
|
//
|
|
// if still can't find a mapping, try using prtupg9x.inf
|
|
//
|
|
|
|
while( !(clientDriverMapped =
|
|
RDPDRUTL_MapPrintDriverName(
|
|
clientDriver,
|
|
L"prtupg9x.inf",
|
|
L"Printer Driver Mapping", 0, 1,
|
|
*mappedName,
|
|
(*mappedNameBufSize) / sizeof(WCHAR),
|
|
&requiredSize
|
|
)) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER) ) {
|
|
if (!UMRDPDR_ResizeBuffer(&MappedDriverNameBuf, requiredSize * sizeof(WCHAR),
|
|
&MappedDriverNameBufSize)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( clientDriverMapped ) {
|
|
goto Done;
|
|
}
|
|
|
|
//
|
|
// If we didn't get a match, then check the "Previous Names" section
|
|
// of ntprint.inf. Source and destination fields are kind of backwards
|
|
// in ntprint.inf.
|
|
//
|
|
if (!clientDriverMapped) {
|
|
while (!(clientDriverMapped =
|
|
RDPDRUTL_MapPrintDriverName(
|
|
clientDriver, L"ntprint.inf",
|
|
L"Previous Names", 1, 0,
|
|
*mappedName,
|
|
(*mappedNameBufSize) / sizeof(WCHAR),
|
|
&requiredSize
|
|
)) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
|
|
if (!UMRDPDR_ResizeBuffer(&MappedDriverNameBuf, requiredSize * sizeof(WCHAR),
|
|
&MappedDriverNameBufSize)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Done:
|
|
|
|
if(clientDriverMapped) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: MapClientPrintDriverName returns %ws.\n", *mappedName));
|
|
}
|
|
|
|
return clientDriverMapped;
|
|
}
|
|
|
|
BOOL
|
|
GetPrinterPortName(
|
|
IN HANDLE hPrinter,
|
|
OUT PWSTR *portName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get the port name for an open printer.
|
|
|
|
Arguments:
|
|
|
|
hPrinter - Open printer handle.
|
|
portName - Port name
|
|
|
|
Return Value:
|
|
|
|
Return TRUE on success. FALSE, otherwise.
|
|
|
|
--*/
|
|
{
|
|
BOOL result;
|
|
DWORD sz;
|
|
|
|
//
|
|
// Size our printer info level 2 buffer.
|
|
//
|
|
result = !GetPrinter(hPrinter, 2, NULL, 0, &sz) &&
|
|
(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
|
|
if (result) {
|
|
result = UMRDPDR_ResizeBuffer(
|
|
&PrinterInfo2Buf,
|
|
sz, &PrinterInfo2BufSize
|
|
);
|
|
}
|
|
|
|
//
|
|
// Get printer info level 2 for the new printer.
|
|
//
|
|
if (result) {
|
|
result = GetPrinter(hPrinter, 2, (char *)PrinterInfo2Buf,
|
|
PrinterInfo2BufSize, &sz);
|
|
}
|
|
|
|
if (result) {
|
|
*portName = &PrinterInfo2Buf->pPortName[0];
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR, ("UMRDPDR:Error fetching printer port name."));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
SaveDefaultPrinterContext(PCWSTR currentlyInstallingPrinterName)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Save current contextual information for the active user's default
|
|
printer, so it can be restored on printer deletion.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
TRUE is returned on success. Otherwise, FALSE is returned and
|
|
GetLastError() can be used for retrieving extended error information.
|
|
|
|
--*/
|
|
{
|
|
BOOL result;
|
|
DWORD bufSize;
|
|
DWORD status = ERROR_SUCCESS;
|
|
BOOL fImpersonated = FALSE;
|
|
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: SaveDefaultPrinterContext entered.\n"));
|
|
|
|
//
|
|
// Save the name of the current default printer, in RAM.
|
|
//
|
|
bufSize = sizeof(SavedDefaultPrinterName) / sizeof(SavedDefaultPrinterName[0]);
|
|
|
|
//
|
|
//impersonate first
|
|
//
|
|
|
|
if (!(fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser))) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:ImpersonateLoggedOnUser failed. Error:%ld.\n", GetLastError()));
|
|
}
|
|
|
|
if (!(result = GetDefaultPrinter(SavedDefaultPrinterName, &bufSize))) {
|
|
status = GetLastError();
|
|
}
|
|
|
|
if (fImpersonated && !RevertToSelf()) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:RevertToSelf failed. Error:%ld.\n", GetLastError()));
|
|
result = FALSE;
|
|
}
|
|
//
|
|
// 645988:Check if the just installed TS printer is the default printer.
|
|
// Since we haven't set it as a TS printer yet, the RDPDRUTL_PrinterIsTs()
|
|
// function will return false and we save this as global default.
|
|
// That will cause problems later when we try to
|
|
// restore the default printer context.
|
|
//
|
|
if (_wcsicmp(currentlyInstallingPrinterName, SavedDefaultPrinterName) == 0) {
|
|
//
|
|
// Clear the default printer name to
|
|
// indicate we haven't found any yet.
|
|
//
|
|
wcscpy(SavedDefaultPrinterName, L"");
|
|
result = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// If the current default printer is a non-TS printer, store its
|
|
// name in a global reg. key for this user. That way, it can be
|
|
// saved when this session or some other session for this user
|
|
// disconnects/logs out.
|
|
//
|
|
if (result) {
|
|
if (!RDPDRUTL_PrinterIsTS(SavedDefaultPrinterName)) {
|
|
result = SavePrinterNameAsGlobalDefault(SavedDefaultPrinterName);
|
|
}
|
|
}
|
|
else {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: Error fetching def printer: %ld.\n",
|
|
status));
|
|
}
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: SaveDefaultPrinterContext exiting.\n"));
|
|
Exit:
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
SavePrinterNameAsGlobalDefault(
|
|
IN PCWSTR printerName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Save the specified printer in the registry so it is visible to all
|
|
other sessions for this user as the last known default printer.
|
|
|
|
Arguments:
|
|
|
|
printerName - Printer name.
|
|
|
|
Return Value:
|
|
|
|
TRUE is returned on success. Otherwise, FALSE is returned and
|
|
GetLastError() can be used for retrieving extended error information.
|
|
|
|
--*/
|
|
{
|
|
BOOL result;
|
|
WCHAR *sidAsText = NULL;
|
|
HKEY regKey = NULL;
|
|
DWORD sz;
|
|
PSID pSid;
|
|
DWORD status;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: SavePrinterNameAsGlobalDefault entered.\n"));
|
|
|
|
//
|
|
// Get the user's SID. This is how we uniquely identify the user.
|
|
//
|
|
pSid = TSNUTL_GetUserSid(UMRPDPPRN_TokenForLoggedOnUser);
|
|
result = pSid != NULL;
|
|
|
|
//
|
|
// Get a textual representation of the session user's SID.
|
|
//
|
|
if (result) {
|
|
sz = 0;
|
|
result = TSNUTL_GetTextualSid(pSid, NULL, &sz);
|
|
ASSERT(!result);
|
|
if (!result && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
|
|
sidAsText = (WCHAR *)ALLOCMEM(sz);
|
|
if (sidAsText != NULL) {
|
|
result = TSNUTL_GetTextualSid(pSid, sidAsText, &sz);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Open the reg key.
|
|
//
|
|
if (result) {
|
|
status = RegCreateKey(
|
|
HKEY_LOCAL_MACHINE, USERDEFAULTPRNREGKEY,
|
|
®Key
|
|
);
|
|
result = status == ERROR_SUCCESS;
|
|
if (!result) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: RegCreateKey failed for %s: %ld.\n",
|
|
USERDEFAULTPRNREGKEY, status));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Write the value for the default printer.
|
|
//
|
|
if (result) {
|
|
sz = (wcslen(printerName) + 1) * sizeof(WCHAR);
|
|
status = RegSetValueEx(regKey, sidAsText, 0, REG_SZ, (PBYTE)printerName, sz);
|
|
result = status == ERROR_SUCCESS;
|
|
if (!result) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: RegSetValueEx failed for %s: %ld.\n",
|
|
sidAsText, status));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
if (sidAsText != NULL) FREEMEM(sidAsText);
|
|
if (regKey != NULL) RegCloseKey(regKey);
|
|
if (pSid != NULL) FREEMEM(pSid);
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: SavePrinterNameAsGlobalDefault exiting.\n"));
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOL
|
|
RestoreDefaultPrinterContext()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Restore the most recent default printer context, as saved via a call to
|
|
SaveDefaultPrinterContext.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
TRUE is returned on success. Otherwise, FALSE is returned and
|
|
GetLastError() can be used for retrieving extended error information.
|
|
|
|
--*/
|
|
{
|
|
BOOL result;
|
|
HANDLE hPrinter;
|
|
PRINTER_DEFAULTS printerDefaults = {NULL, NULL, PRINTER_ACCESS_USE};
|
|
|
|
WCHAR *sidAsText = NULL;
|
|
HKEY regKey = NULL;
|
|
DWORD sz;
|
|
PSID pSid = NULL;
|
|
DWORD status;
|
|
|
|
WCHAR savedDefaultPrinter[MAX_PATH];
|
|
|
|
WCHAR *nameToRestore = NULL;
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: RestoreDefaultPrinterContext entered.\n"));
|
|
|
|
//
|
|
// Assume that we will succeed.
|
|
//
|
|
result = TRUE;
|
|
|
|
//
|
|
// Restore the default printer name stored in RAM, if it exists.
|
|
//
|
|
if (wcscmp(SavedDefaultPrinterName, L"") &&
|
|
OpenPrinter(SavedDefaultPrinterName, &hPrinter, &printerDefaults)) {
|
|
ClosePrinter(hPrinter);
|
|
nameToRestore = &SavedDefaultPrinterName[0];
|
|
}
|
|
|
|
//
|
|
// If the default printer name saved in RAM does not exist, then we need
|
|
// to save the one that is saved in the registry, if it exists.
|
|
//
|
|
if (nameToRestore == NULL) {
|
|
|
|
BOOL intermediateResult;
|
|
|
|
//
|
|
// Get the user's SID. This is how we uniquely identify the user.
|
|
//
|
|
pSid = TSNUTL_GetUserSid(UMRPDPPRN_TokenForLoggedOnUser);
|
|
intermediateResult = pSid != NULL;
|
|
|
|
//
|
|
// Get a textual representation of the session user's SID.
|
|
//
|
|
if (intermediateResult) {
|
|
sz = 0;
|
|
intermediateResult = TSNUTL_GetTextualSid(pSid, NULL, &sz);
|
|
ASSERT(!intermediateResult);
|
|
if (!intermediateResult && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
|
|
sidAsText = (WCHAR *)ALLOCMEM(sz);
|
|
if (sidAsText != NULL) {
|
|
intermediateResult = TSNUTL_GetTextualSid(pSid, sidAsText, &sz);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Open the reg key.
|
|
//
|
|
if (intermediateResult) {
|
|
status = RegCreateKey(
|
|
HKEY_LOCAL_MACHINE, USERDEFAULTPRNREGKEY,
|
|
®Key
|
|
);
|
|
intermediateResult = status == ERROR_SUCCESS;
|
|
if (!intermediateResult) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: RegCreateKey failed for %s: %ld.\n",
|
|
USERDEFAULTPRNREGKEY, status));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Read the value.
|
|
//
|
|
if (intermediateResult) {
|
|
DWORD type;
|
|
DWORD sz = sizeof(savedDefaultPrinter);
|
|
status = RegQueryValueEx(
|
|
regKey, sidAsText, 0,
|
|
&type, (PBYTE)savedDefaultPrinter, &sz
|
|
);
|
|
if (status == ERROR_SUCCESS) {
|
|
intermediateResult = TRUE;
|
|
ASSERT(type == REG_SZ);
|
|
nameToRestore = savedDefaultPrinter;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we got a value, then that means we will be restoring the
|
|
// one from the registry. That also means that we should whack the
|
|
// registry value to be a good citizen.
|
|
//
|
|
if (intermediateResult) {
|
|
status = RegDeleteValue(regKey, sidAsText);
|
|
if (status != ERROR_SUCCESS) {
|
|
DBGMSG(DBG_ERROR, ("UMRDPPRN: Can't delete reg value %s: %ld\n",
|
|
sidAsText, status));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have a name to restore, then do so.
|
|
//
|
|
if (nameToRestore != NULL) {
|
|
|
|
BOOL fImpersonated = FALSE;
|
|
|
|
//
|
|
//impersonate before setting the default printer as the api
|
|
//accesses hkcu. If the impersonation fails, the api will fail
|
|
//and we will log an error. But, before logging an error, we will
|
|
//need to revert to self.
|
|
//
|
|
if (!(fImpersonated = ImpersonateLoggedOnUser(UMRPDPPRN_TokenForLoggedOnUser))) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:ImpersonateLoggedOnUser failed. Error:%ld.\n", GetLastError()));
|
|
}
|
|
|
|
result = SetDefaultPrinter(nameToRestore);
|
|
|
|
//
|
|
//if revert to self fails, consider it fatal
|
|
//
|
|
if (fImpersonated && !RevertToSelf()) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPDR:RevertToSelf failed. Error:%ld.\n", GetLastError()));
|
|
result = FALSE;
|
|
}
|
|
|
|
|
|
if (!result) {
|
|
WCHAR * param = nameToRestore;
|
|
TsLogError(EVENT_NOTIFY_SETDEFAULTPRINTER_FAILED,
|
|
EVENTLOG_ERROR_TYPE,
|
|
1,
|
|
¶m,
|
|
__LINE__);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we don't have a name to restore of the restore failed, then
|
|
// just restore the first printer we find.
|
|
//
|
|
if (nameToRestore == NULL) {
|
|
|
|
//
|
|
// If we still don't have a printer name to restore, then we should
|
|
// just restore the first printer we find.
|
|
//
|
|
result = SetDefaultPrinterToFirstFound(TRUE);
|
|
}
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
if (sidAsText != NULL) FREEMEM(sidAsText);
|
|
if (regKey != NULL) RegCloseKey(regKey);
|
|
if (pSid != NULL) FREEMEM(pSid);
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN: RestoreDefaultPrinterContext exiting.\n"));
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
BOOL SplitName(
|
|
IN OUT LPTSTR pszFullName,
|
|
OUT LPCTSTR *ppszServer,
|
|
OUT LPCTSTR *ppszPrinter,
|
|
IN BOOL bCheckDoubleSlash)
|
|
|
|
/*++
|
|
|
|
Splits a fully qualified printer connection name into server and
|
|
printer name parts. If the function fails then none of the OUT
|
|
parameters are modified.
|
|
|
|
Arguments:
|
|
|
|
pszFullName - Input name of a printer. If it is a printer
|
|
connection (\\server\printer), then we will split it.
|
|
|
|
ppszServer - Receives pointer to the server string.
|
|
|
|
ppszPrinter - Receives a pointer to the printer string.
|
|
|
|
bCheckDoubleSlash - if TRUE check that the name begins with "\\".
|
|
If FALSE, the first character is the first character of the server.
|
|
|
|
Return Value: TRUE if everything is found as expected. FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
LPTSTR pszPrinter;
|
|
LPTSTR pszTmp;
|
|
|
|
if (bCheckDoubleSlash) {
|
|
|
|
if (pszFullName[0] == TEXT('!') && pszFullName[1] == TEXT('!')) {
|
|
|
|
pszTmp = pszFullName + 2;
|
|
|
|
} else {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
} else {
|
|
|
|
pszTmp = pszFullName;
|
|
}
|
|
|
|
pszPrinter = wcschr(pszTmp, TEXT('!'));
|
|
|
|
if (pszPrinter)
|
|
{
|
|
//
|
|
// We found the backslash; null terminate the previous
|
|
// name.
|
|
//
|
|
*pszPrinter++ = 0;
|
|
|
|
*ppszServer = pszTmp;
|
|
*ppszPrinter = pszPrinter;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void FormatPrinterName(
|
|
PWSTR pszNewNameBuf,
|
|
ULONG ulBufLen,
|
|
ULONG ulFlags,
|
|
PTS_PRINTER_NAMES pPrinterNames)
|
|
{
|
|
|
|
WCHAR szSessionId[MAXSESSIONIDCHARS];
|
|
PWSTR pszRet = NULL;
|
|
PWSTR pszFormat;
|
|
DWORD dwBytes = 0;
|
|
const WCHAR * pStrings[4];
|
|
|
|
lstrcpyn(pPrinterNames->szTemp, pPrinterNames->pszFullName, pPrinterNames->ulTempLen);
|
|
|
|
// TS and network printer: \\Server\Client\Printer
|
|
// non TS network printer: \\Server\Printer
|
|
// TS non network printer: \\Client\Printer
|
|
|
|
if (ulFlags & RDPDR_PRINTER_ANNOUNCE_FLAG_NETWORKPRINTER) {
|
|
|
|
if (SplitName(pPrinterNames->szTemp,
|
|
&(pPrinterNames->pszServer),
|
|
&(pPrinterNames->pszPrinter),
|
|
TRUE)) {
|
|
|
|
// We found a Server name, in any case we'll have
|
|
// something like "Printer on Server (from...)".
|
|
pszFormat = g_szOnFromFormat;
|
|
|
|
if (ulFlags & RDPDR_PRINTER_ANNOUNCE_FLAG_TSPRINTER ) {
|
|
|
|
if(!SplitName((PWSTR)(pPrinterNames->pszPrinter),
|
|
&(pPrinterNames->pszClient),
|
|
&(pPrinterNames->pszPrinter),
|
|
FALSE)) {
|
|
|
|
// The original client name could not be found,
|
|
// use the curent one.
|
|
pPrinterNames->pszClient = pPrinterNames->pszCurrentClient;
|
|
}
|
|
|
|
} else {
|
|
|
|
pPrinterNames->pszClient = pPrinterNames->pszCurrentClient;
|
|
}
|
|
|
|
} else {
|
|
|
|
// The name of the server could not be found!
|
|
// Use the original name.
|
|
pszFormat = g_szFromFormat;
|
|
pPrinterNames->pszPrinter = pPrinterNames->pszFullName;
|
|
pPrinterNames->pszClient = pPrinterNames->pszCurrentClient;
|
|
}
|
|
|
|
} else {
|
|
|
|
// It's not a network printer, so we'll have
|
|
// something like "Printer (from Client)".
|
|
pszFormat = g_szFromFormat;
|
|
|
|
if (ulFlags & RDPDR_PRINTER_ANNOUNCE_FLAG_TSPRINTER ) {
|
|
|
|
if(!SplitName(pPrinterNames->szTemp,
|
|
&(pPrinterNames->pszClient),
|
|
&(pPrinterNames->pszPrinter),
|
|
TRUE)) {
|
|
|
|
pPrinterNames->pszPrinter = pPrinterNames->pszFullName;
|
|
pPrinterNames->pszClient = pPrinterNames->pszCurrentClient;
|
|
}
|
|
|
|
} else {
|
|
|
|
pPrinterNames->pszPrinter = pPrinterNames->pszFullName;
|
|
pPrinterNames->pszClient = pPrinterNames->pszCurrentClient;
|
|
}
|
|
}
|
|
|
|
|
|
pStrings[0] = pPrinterNames->pszPrinter;
|
|
pStrings[1] = pPrinterNames->pszServer;
|
|
pStrings[2] = pPrinterNames->pszClient;
|
|
|
|
if (g_fIsPTS) {
|
|
pStrings[3] = NULL;
|
|
} else {
|
|
wsprintf(szSessionId, L"%ld", GETTHESESSIONID());
|
|
pStrings[3] = szSessionId;
|
|
}
|
|
|
|
|
|
if (*pszFormat) {
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:formating %ws, %ws and %ws\n", pStrings[0], pStrings[1],
|
|
pStrings[2]?pStrings[2]:L""));
|
|
|
|
dwBytes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_STRING |
|
|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
pszFormat,
|
|
0,
|
|
0,
|
|
(LPTSTR)&pszRet,
|
|
0,
|
|
(va_list*)pStrings);
|
|
|
|
DBGMSG(DBG_TRACE, ("UMRDPPRN:formated %ws\n", pszRet));
|
|
}
|
|
|
|
//
|
|
// Copy the new name.
|
|
//
|
|
if ( dwBytes && pszRet ) {
|
|
wcsncpy(pszNewNameBuf, pszRet, ulBufLen);
|
|
} else {
|
|
wcsncpy(pszNewNameBuf, pPrinterNames->pszFullName, ulBufLen);
|
|
}
|
|
|
|
pszNewNameBuf[ulBufLen] = L'\0';
|
|
|
|
//
|
|
// Release the formated string.
|
|
//
|
|
if( pszRet ) {
|
|
LocalFree(pszRet);
|
|
}
|
|
}
|
|
|
|
|