/*++ Copyright (c) 1990 - 1995 Microsoft Corporation Module Name: util.c Abstract: This module provides all the utility functions for the Routing Layer and the local Print Providor Author: Dave Snipp (DaveSn) 15-Mar-1991 Revision History: Felix Maxa (amaxa) 18-Jun-2000 Added utility functions for cluster spoolers. Part of the DCR regarding installation of printer drivers on cluster spoolers Muhunthan Sivapragasam ( MuhuntS ) 5-June-1995 Moved from printer.c: RegSetBinaryData RegSetString RegSetDWord Wrote: SameMultiSz RegGetValue Matthew A Felton ( MattFe ) 23-mar-1995 DeleteAllFilesAndDirectory DeleteAllFilesInDirectory CreateDirectoryWithoutImpersonatingUser --*/ #include #pragma hdrstop #include #include #include #include #include "clusspl.h" typedef LONG (WINAPI *pfnWinStationSendWindowMessage)( HANDLE hServer, ULONG sessionID, ULONG timeOut, ULONG hWnd, ULONG Msg, WPARAM wParam, LPARAM lParam, LONG *pResponse); extern BOOL (*pfnOpenPrinter)(LPTSTR, LPHANDLE, LPPRINTER_DEFAULTS); extern BOOL (*pfnClosePrinter)(HANDLE); extern LONG (*pfnDocumentProperties)(HWND, HANDLE, LPWSTR, PDEVMODE, PDEVMODE, DWORD); #define DEFAULT_MAX_TIMEOUT 300000 // 5 minute timeout. CRITICAL_SECTION SpoolerSection; PDBG_POINTERS gpDbgPointers = NULL; pfnWinStationSendWindowMessage pfWinStationSendWindowMessage = NULL; VOID RunForEachSpooler( HANDLE h, PFNSPOOLER_MAP pfnMap ) { PINISPOOLER pIniSpooler; PINISPOOLER pIniNextSpooler; SplInSem(); if( pLocalIniSpooler ){ INCSPOOLERREF( pLocalIniSpooler ); for( pIniSpooler = pLocalIniSpooler; pIniSpooler; pIniSpooler = pIniNextSpooler ){ if( (*pfnMap)( h, pIniSpooler ) ){ pIniNextSpooler = pIniSpooler->pIniNextSpooler; } else { pIniNextSpooler = NULL; } if( pIniNextSpooler ){ INCSPOOLERREF( pIniNextSpooler ); } DECSPOOLERREF( pIniSpooler ); } } } VOID RunForEachPrinter( PINISPOOLER pIniSpooler, HANDLE h, PFNPRINTER_MAP pfnMap ) { PINIPRINTER pIniPrinter; PINIPRINTER pIniNextPrinter; SplInSem(); pIniPrinter = pIniSpooler->pIniPrinter; if( pIniPrinter ){ INCPRINTERREF( pIniPrinter ); for( ; pIniPrinter; pIniPrinter = pIniNextPrinter ){ if( (*pfnMap)( h, pIniPrinter ) ){ pIniNextPrinter = pIniPrinter->pNext; } else { pIniNextPrinter = NULL; } if( pIniNextPrinter ){ INCPRINTERREF( pIniNextPrinter ); } DECPRINTERREF( pIniPrinter ); DeletePrinterCheck( pIniPrinter ); } } } #if DBG HANDLE hcsSpoolerSection = NULL; VOID SplInSem( VOID ) { if( hcsSpoolerSection ){ SPLASSERT( gpDbgPointers->pfnInsideCritSec( hcsSpoolerSection )); } else { SPLASSERT( SpoolerSection.OwningThread == (HANDLE)(ULONG_PTR)(GetCurrentThreadId( ))); } } VOID SplOutSem( VOID ) { if( hcsSpoolerSection ){ SPLASSERT( gpDbgPointers->pfnOutsideCritSec( hcsSpoolerSection )); } else { SPLASSERT( SpoolerSection.OwningThread != (HANDLE)((ULONG_PTR)GetCurrentThreadId( ))); } } #endif // DBG VOID EnterSplSem( VOID ) { #if DBG if( hcsSpoolerSection ){ gpDbgPointers->pfnEnterCritSec( hcsSpoolerSection ); } else { EnterCriticalSection( &SpoolerSection ); } #else EnterCriticalSection( &SpoolerSection ); #endif } VOID LeaveSplSem( VOID ) { #if DBG if( hcsSpoolerSection ){ gpDbgPointers->pfnLeaveCritSec( hcsSpoolerSection ); } else { LeaveCriticalSection( &SpoolerSection ); } #else LeaveCriticalSection( &SpoolerSection ); #endif } PDEVMODE AllocDevMode( PDEVMODE pDevMode ) { PDEVMODE pDevModeAlloc = NULL; DWORD Size; if (pDevMode) { Size = pDevMode->dmSize + pDevMode->dmDriverExtra; if(pDevModeAlloc = AllocSplMem(Size)) { memcpy(pDevModeAlloc, pDevMode, Size); } } return pDevModeAlloc; } BOOL FreeDevMode( PDEVMODE pDevMode ) { if (pDevMode) { FreeSplMem((PVOID)pDevMode); return TRUE; } else { return FALSE; } } PINIENTRY FindName( PINIENTRY pIniKey, LPWSTR pName ) { if (pName) { while (pIniKey) { if (!lstrcmpi(pIniKey->pName, pName)) { return pIniKey; } pIniKey=pIniKey->pNext; } } return FALSE; } BOOL FileExists( LPWSTR pFileName ) { if( GetFileAttributes( pFileName ) == 0xffffffff ){ return FALSE; } return TRUE; } BOOL DirectoryExists( LPWSTR pDirectoryName ) { DWORD dwFileAttributes; dwFileAttributes = GetFileAttributes( pDirectoryName ); if ( dwFileAttributes != 0xffffffff && dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { return TRUE; } return FALSE; } BOOL CheckSepFile( IN LPWSTR pFileName ) { BOOL bRetval = FALSE; // // NULL or "" is OK: // if (!pFileName || !*pFileName) { bRetval = TRUE; } else { // // If the name is not NULL or "" then the name must be less // than MAX_PATH and exist. // if ((wcslen(pFileName) < MAX_PATH-1) && FileExists(pFileName)) { bRetval = TRUE; } } return bRetval; } DWORD GetFullNameFromId( PINIPRINTER pIniPrinter, DWORD JobId, BOOL fJob, LPWSTR pFileName, BOOL Remote ) { DWORD i; // // MAX_PATH - 9 is tha maximum number of chars that we want to store in pFileName since we // want to concatenate the SPL/SHD file // If GetPrinterDirectory fails i is 0. // The right way to fix this is that the caller of GetFullNameFromId chackes for the return value // which is not the case. // i = GetPrinterDirectory(pIniPrinter, Remote, pFileName, MAX_PATH-9, pIniPrinter->pIniSpooler); pFileName[i++]=L'\\'; wsprintf(&pFileName[i], L"%05d.%ws", JobId, fJob ? L"SPL" : L"SHD"); #ifdef PREVIOUS for (i = 5; i--;) { pFileName[i++] = (CHAR)((JobId % 10) + '0'); JobId /= 10; } #endif while (pFileName[i++]) ; return i-1; } DWORD GetPrinterDirectory( PINIPRINTER pIniPrinter, // Can be NULL BOOL Remote, LPWSTR pDir, DWORD MaxLength, PINISPOOLER pIniSpooler ) { DWORD i=0; LPWSTR psz; if (Remote) { DBGMSG(DBG_ERROR, ("GetPrinterDirectory called remotely. Not currently supported.")); return 0; } if ((pIniPrinter == NULL) || (pIniPrinter->pSpoolDir == NULL) ) { if (pIniSpooler->pDefaultSpoolDir == NULL) { // // No default directory, then create a default. For cluster spoolers, // the default directory is N:\Spool, where N is the shared drive letter // if( StrNCatBuff(pDir, MaxLength, pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER ? pIniSpooler->pszClusResDriveLetter : pIniSpooler->pDir, L"\\", pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER ? szClusterPrinterDir : szPrinterDir, NULL) != ERROR_SUCCESS ) { return 0; } pIniSpooler->pDefaultSpoolDir = AllocSplStr(pDir); } else { // Give Caller the Default wcscpy(pDir, pIniSpooler->pDefaultSpoolDir); } } else { // Have Per Printer Directory wcscpy (pDir, pIniPrinter->pSpoolDir); } return (wcslen(pDir)); } DWORD GetDriverDirectory( LPWSTR pDir, DWORD MaxLength, PINIENVIRONMENT pIniEnvironment, LPWSTR lpRemotePath, PINISPOOLER pIniSpooler ) { LPWSTR psz; if (lpRemotePath) { if( StrNCatBuff(pDir, MaxLength, lpRemotePath, L"\\", pIniSpooler->pszDriversShare, L"\\", pIniEnvironment->pDirectory, NULL) != ERROR_SUCCESS ) { return 0; } } else { if( StrNCatBuff( pDir, MaxLength, pIniSpooler->pDir, L"\\", szDriverDir, L"\\", pIniEnvironment->pDirectory, NULL) != ERROR_SUCCESS ) { return 0; } } return wcslen(pDir); } DWORD GetProcessorDirectory( LPWSTR *pDir, LPWSTR pEnvironment, PINISPOOLER pIniSpooler ) { return StrCatAlloc(pDir, pIniSpooler->pDir, L"\\", szPrintProcDir, L"\\", pEnvironment, NULL); } PINIENTRY FindIniKey( PINIENTRY pIniEntry, LPWSTR pName ) { if ( pName == NULL ) { return NULL; } SplInSem(); while ( pIniEntry && lstrcmpi( pName, pIniEntry->pName )) pIniEntry = pIniEntry->pNext; return pIniEntry; } BOOL CreateCompleteDirectory( LPWSTR pDir ) { LPWSTR pBackSlash=pDir; do { pBackSlash = wcschr( pBackSlash, L'\\' ); if ( pBackSlash != NULL ) *pBackSlash = 0; CreateDirectory(pDir, NULL); if ( pBackSlash ) *pBackSlash++=L'\\'; } while ( pBackSlash ); // BUBUG Always returns TRUE return TRUE; } LPCWSTR FindFileName( LPCWSTR pPathName ) /*++ Routine Description: Retrieve the filename portion of a path. This will can the input string until it finds the last backslash, then return the portion of the string immediately following it. If the string terminates with a backslash, then NULL is returned. Note: this can return illegal file names; no validation is done. Arguments: pPathName - Path name to parse. Return Value: Last portion of file name or NULL if none available. --*/ { LPCWSTR pSlash; LPCWSTR pTemp; if( !pPathName ){ return NULL; } pTemp = pPathName; while( pSlash = wcschr( pTemp, L'\\' )) { pTemp = pSlash+1; } if( !*pTemp ){ return NULL; } return pTemp; } LPWSTR GetFileName( LPWSTR pPathName ) { LPCWSTR pFileName; pFileName = FindFileName( pPathName ); if (pFileName) { return( AllocSplStr( pFileName ) ); } else { return(NULL); } } VOID CreatePrintProcDirectory( LPWSTR pEnvironment, PINISPOOLER pIniSpooler ) { DWORD cb; LPWSTR pEnd; LPWSTR pPathName; cb = wcslen(pIniSpooler->pDir)*sizeof(WCHAR) + wcslen(pEnvironment)*sizeof(WCHAR) + wcslen(szPrintProcDir)*sizeof(WCHAR) + 4*sizeof(WCHAR); if (pPathName=AllocSplMem(cb)) { wcscpy(pPathName, pIniSpooler->pDir); pEnd=pPathName+wcslen(pPathName); if( CreateDirectory(pPathName, NULL) || ( GetLastError() == ERROR_ALREADY_EXISTS )) { wcscpy(pEnd++, L"\\"); wcscpy(pEnd, szPrintProcDir); if( CreateDirectory(pPathName, NULL) || ( GetLastError() == ERROR_ALREADY_EXISTS )) { pEnd+=wcslen(pEnd); wcscpy(pEnd++, L"\\"); wcscpy(pEnd, pEnvironment); if (CreateDirectory(pPathName, NULL) || (GetLastError() == ERROR_ALREADY_EXISTS)) { pEnd+=wcslen(pEnd); } } } FreeSplMem(pPathName); } } BOOL RemoveFromList( PINIENTRY *ppIniHead, PINIENTRY pIniEntry ) { while (*ppIniHead && *ppIniHead != pIniEntry) { ppIniHead = &(*ppIniHead)->pNext; } if (*ppIniHead) *ppIniHead = (*ppIniHead)->pNext; return(TRUE); } PKEYDATA CreateTokenList( LPWSTR pKeyData ) { DWORD cTokens; DWORD cb; PKEYDATA pResult; LPWSTR pDest; LPWSTR psz = pKeyData; LPWSTR *ppToken; if (!psz || !*psz) return NULL; cTokens=1; // Scan through the string looking for commas, // ensuring that each is followed by a non-NULL character: while ((psz = wcschr(psz, L',')) && psz[1]) { cTokens++; psz++; } cb = sizeof(KEYDATA) + (cTokens-1) * sizeof(LPWSTR) + wcslen(pKeyData)*sizeof(WCHAR) + sizeof(WCHAR); if (!(pResult = (PKEYDATA)AllocSplMem(cb))) return NULL; // Initialise pDest to point beyond the token pointers: pDest = (LPWSTR)((LPBYTE)pResult + sizeof(KEYDATA) + (cTokens-1) * sizeof(LPWSTR)); // Then copy the key data buffer there: wcscpy(pDest, pKeyData); ppToken = pResult->pTokens; psz = pDest; do { *ppToken++ = psz; if ( psz = wcschr(psz, L',') ) *psz++ = L'\0'; } while (psz); pResult->cTokens = cTokens; return( pResult ); } VOID FreePortTokenList( PKEYDATA pKeyData ) { PINIPORT pIniPort; DWORD i; if ( pKeyData ) { if ( pKeyData->bFixPortRef ) { for ( i = 0 ; i < pKeyData->cTokens ; ++i ) { pIniPort = (PINIPORT)pKeyData->pTokens[i]; DECPORTREF(pIniPort); } } FreeSplMem(pKeyData); } } VOID GetPrinterPorts( PINIPRINTER pIniPrinter, LPWSTR pszPorts, DWORD *pcbNeeded ) { PINIPORT pIniPort; BOOL Comma; DWORD i; DWORD cbNeeded = 0; SPLASSERT(pcbNeeded); // Determine required size Comma = FALSE; for ( i = 0 ; i < pIniPrinter->cPorts ; ++i ) { pIniPort = pIniPrinter->ppIniPorts[i]; if ( pIniPort->Status & PP_FILE ) continue; if ( Comma ) cbNeeded += wcslen(szComma)*sizeof(WCHAR); cbNeeded += wcslen(pIniPort->pName)*sizeof(WCHAR); Comma = TRUE; } // // Add in size of NULL // cbNeeded += sizeof(WCHAR); if (pszPorts && cbNeeded <= *pcbNeeded) { // // If we are given a buffer & buffer is big enough, then fill it // Comma = FALSE; for ( i = 0 ; i < pIniPrinter->cPorts ; ++i ) { pIniPort = pIniPrinter->ppIniPorts[i]; if ( pIniPort->Status & PP_FILE ) continue; if ( Comma ) { wcscat(pszPorts, szComma); wcscat(pszPorts, pIniPort->pName); } else { wcscpy(pszPorts, pIniPort->pName); } Comma = TRUE; } } *pcbNeeded = cbNeeded; } BOOL MyName( LPWSTR pName, PINISPOOLER pIniSpooler ) { EnterSplSem(); if (CheckMyName(pName, pIniSpooler)) { LeaveSplSem(); return TRUE; } // // Only refresh machine names if pName is an IP or DNS address // if (pIniSpooler == pLocalIniSpooler && wcschr(pName, L'.') && RefreshMachineNamesCache() && CheckMyName(pName, pIniSpooler)) { LeaveSplSem(); return TRUE; } SetLastError(ERROR_INVALID_NAME); LeaveSplSem(); return FALSE; } BOOL RefreshMachineNamesCache( ) { PWSTR *ppszOtherNames; DWORD cOtherNames; SplInSem(); // // Get other machine names first. Only if it succeeds do we replace the cache. // if (!BuildOtherNamesFromMachineName(&ppszOtherNames, &cOtherNames)) return FALSE; FreeOtherNames(&pLocalIniSpooler->ppszOtherNames, &pLocalIniSpooler->cOtherNames); pLocalIniSpooler->ppszOtherNames = ppszOtherNames; pLocalIniSpooler->cOtherNames = cOtherNames; return TRUE; } BOOL CheckMyName( LPWSTR pName, PINISPOOLER pIniSpooler ) { DWORD dwIndex = 0; if (!pName || !*pName) return TRUE; if (*pName == L'\\' && *(pName+1) == L'\\') { if (!lstrcmpi(pName, pIniSpooler->pMachineName)) return TRUE; while (dwIndex < pIniSpooler->cOtherNames) { if (!lstrcmpi(pName+2, pIniSpooler->ppszOtherNames[dwIndex])) return TRUE; ++dwIndex; } } return FALSE; } BOOL GetSid( PHANDLE phToken ) { if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_QUERY, TRUE, phToken)) { DBGMSG(DBG_WARNING, ("OpenThreadToken failed: %d\n", GetLastError())); return FALSE; } else return TRUE; } BOOL SetCurrentSid( HANDLE hToken ) { #if DBG WCHAR UserName[256]; DWORD cbUserName=256; if( MODULE_DEBUG & DBG_TRACE ) GetUserName(UserName, &cbUserName); DBGMSG(DBG_TRACE, ("SetCurrentSid BEFORE: user name is %ws\n", UserName)); #endif // // Normally the function SetCurrentSid is not supposed to change the last error // of the routine where it is called. NtSetInformationThread conveniently returns // a status and does not touch the last error. // NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hToken, sizeof(hToken)); #if DBG cbUserName = 256; if( MODULE_DEBUG & DBG_TRACE ) GetUserName(UserName, &cbUserName); DBGMSG(DBG_TRACE, ("SetCurrentSid AFTER: user name is %ws\n", UserName)); #endif return TRUE; } LPWSTR GetErrorString( DWORD Error ) { WCHAR Buffer1[512]; LPWSTR pErrorString=NULL; DWORD dwFlags; HANDLE hModule = NULL; if ((Error >= NERR_BASE) && (Error <= MAX_NERR)) { dwFlags = FORMAT_MESSAGE_FROM_HMODULE; hModule = LoadLibrary(szNetMsgDll); } else { dwFlags = FORMAT_MESSAGE_FROM_SYSTEM; hModule = NULL; } // // Only display out of paper and device disconnected errors. // if ((Error == ERROR_NOT_READY || Error == ERROR_OUT_OF_PAPER || Error == ERROR_DEVICE_REINITIALIZATION_NEEDED || Error == ERROR_DEVICE_REQUIRES_CLEANING || Error == ERROR_DEVICE_DOOR_OPEN || Error == ERROR_DEVICE_NOT_CONNECTED) && FormatMessage(dwFlags, hModule, Error, 0, Buffer1, COUNTOF(Buffer1), NULL)) { EnterSplSem(); pErrorString = AllocSplStr(Buffer1); LeaveSplSem(); } if (hModule) { FreeLibrary(hModule); } return pErrorString; } #define NULL_TERMINATED 0 INT AnsiToUnicodeString( LPSTR pAnsi, LPWSTR pUnicode, DWORD StringLength ) /*++ Routine Description: Converts an Ansi String to a UnicodeString Arguments: pAnsi - A valid source ANSI string. pUnicode - A pointer to a buffer large enough to accommodate the converted string. StringLength - The length of the source ANSI string. If 0 (NULL_TERMINATED), the string is assumed to be null-terminated. Return Value: The return value from MultiByteToWideChar, the number of wide characters returned. andrewbe, 11 Jan 1993 --*/ { if( StringLength == NULL_TERMINATED ) StringLength = strlen( pAnsi ); return MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, pAnsi, StringLength + 1, pUnicode, StringLength + 1 ); } INT Message( HWND hwnd, DWORD Type, int CaptionID, int TextID, ...) { /*++ Routine Description: Displays a message by loading the strings whose IDs are passed into the function, and substituting the supplied variable argument list using the varargs macros. Arguments: hwnd Window Handle Type CaptionID TextId Return Value: --*/ WCHAR MsgText[512]; WCHAR MsgFormat[256]; WCHAR MsgCaption[40]; va_list vargs; if( ( LoadString( hInst, TextID, MsgFormat, sizeof MsgFormat / sizeof *MsgFormat ) > 0 ) && ( LoadString( hInst, CaptionID, MsgCaption, sizeof MsgCaption / sizeof *MsgCaption ) > 0 ) ) { va_start( vargs, TextID ); _vsntprintf( MsgText, COUNTOF(MsgText)-1, MsgFormat, vargs ); MsgText[COUNTOF(MsgText)-1] = 0; va_end( vargs ); return MessageBox(hwnd, MsgText, MsgCaption, Type); } else return 0; } typedef struct { DWORD Message; WPARAM wParam; LPARAM lParam; } MESSAGE, *PMESSAGE; // The Broadcasts are done on a separate thread, the reason it CSRSS // will create a server side thread when we call user and we don't want // that to be paired up with the RPC thread which is in the spooss server. // We want it to go away the moment we have completed the SendMessage. // We also call SendNotifyMessage since we don't care if the broadcasts // are syncronous this uses less resources since usually we don't have more // than one broadcast. // // TESTING // DWORD dwSendFormMessage = 0; VOID SplBroadcastChange( HANDLE hPrinter, DWORD Message, WPARAM wParam, LPARAM lParam ) { PSPOOL pSpool = (PSPOOL)hPrinter; PINISPOOLER pIniSpooler; if (ValidateSpoolHandle( pSpool, 0 )) { pIniSpooler = pSpool->pIniSpooler; BroadcastChange(pIniSpooler, Message, wParam, lParam); } } VOID BroadcastChange( IN PINISPOOLER pIniSpooler, IN DWORD Message, IN WPARAM wParam, IN LPARAM lParam ) { if (( pIniSpooler != NULL ) && ( pIniSpooler->SpoolerFlags & SPL_BROADCAST_CHANGE )) { BOOL bIsTerminalServerInstalled = (USER_SHARED_DATA->SuiteMask & (1 << TerminalServer)); // // Currently we cannot determine if the TermService process is running, so at the momemt // we assume it is always running. // BOOL bIsTerminalServerRunning = TRUE; // // If terminal server is installed and enabled then load the winsta.dll if not already // loaded and get the send window message function. // if ( bIsTerminalServerInstalled && !pfWinStationSendWindowMessage ) { // // The winstadllhandle is shared among other files in the spooler, so don't // load the dll again if it is already loaded. Note: we are not in a critical // section because winsta.dll is never unload, hence if there are two threads // that execute this code at the same time we may potenially load the library // twice. // if ( !WinStaDllHandle ) { UINT uOldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); WinStaDllHandle = LoadLibraryW(L"winsta.dll"); SetErrorMode(uOldErrorMode); } if ( WinStaDllHandle ) { pfWinStationSendWindowMessage = (pfnWinStationSendWindowMessage)GetProcAddress( WinStaDllHandle, "WinStationSendWindowMessage" ); } } if ( pfWinStationSendWindowMessage ) { // // Only send the message to the session that orginated // the call, this will go to the console session when a // change is made by a remote client. // LONG Response = 0; LONG lRetval = FALSE; HANDLE hToken = NULL; ULONG uSession = GetClientSessionId(); // // It appears the WinStationSendWindowMessage function has to // be in system context if the impersonating user is not an // an admin on the machine. // hToken = RevertToPrinterSelf(); lRetval = pfWinStationSendWindowMessage( SERVERNAME_CURRENT, uSession, 1, // Wait at most one second HandleToULong(HWND_BROADCAST), Message, wParam, lParam, &Response ); ImpersonatePrinterClient(hToken); } // // We send the message normally if we have a null pfWinstationSendWindowMessage // function or if terminal server is not running. // if ( !pfWinStationSendWindowMessage || !bIsTerminalServerRunning ){ SendNotifyMessage( HWND_BROADCAST, Message, wParam, lParam ); } } else { DBGMSG(DBG_TRACE, ("BroadCastChange Ignoring Change\n")); } } VOID MyMessageBeep( DWORD fuType, PINISPOOLER pIniSpooler ) { if ( pIniSpooler->dwBeepEnabled != 0 ) { MessageBeep(fuType); } } // Recursively delete any subkeys of a given key. // Assumes that RevertToPrinterSelf() has been called. DWORD DeleteSubkeys( HKEY hKey, PINISPOOLER pIniSpooler ) { DWORD cchData; WCHAR SubkeyName[MAX_PATH]; HKEY hSubkey; LONG Status; cchData = COUNTOF( SubkeyName ); while ((Status = SplRegEnumKey( hKey, 0, SubkeyName, &cchData, NULL, pIniSpooler )) == ERROR_SUCCESS ) { Status = SplRegCreateKey( hKey, SubkeyName, 0, KEY_READ | KEY_WRITE, NULL, &hSubkey, NULL, pIniSpooler ); if( Status == ERROR_SUCCESS ) { Status = DeleteSubkeys( hSubkey, pIniSpooler ); SplRegCloseKey( hSubkey, pIniSpooler); if( Status == ERROR_SUCCESS ) SplRegDeleteKey( hKey, SubkeyName, pIniSpooler ); } // // N.B. Don't increment since we've deleted the zeroth item. // cchData = COUNTOF( SubkeyName ); } if( Status == ERROR_NO_MORE_ITEMS ) Status = ERROR_SUCCESS; return Status; } long Myatol(LPWSTR nptr) { int c; // current char long total; // current total int sign; // if '-', then negative, otherwise positive // skip whitespace while (isspace(*nptr)) ++nptr; c = *nptr++; sign = c; // save sign indication if (c == '-' || c == '+') c = *nptr++; // skip sign total = 0; while (isdigit(c)) { total = 10 * total + (c - '0'); // accumulate digit c = *nptr++; // get next char } if (sign == '-') return -total; else return total; // return result, negated if necessary } ULONG_PTR atox( LPCWSTR psz ) /*++ Routine Description: Converts a string to a hex value, skipping any leading white space. Cannot be uppercase, cannot contain leading 0x. Arguments: psz - pointer to hex string that needs to be converted. This string can have leading characters, but MUST be lowercase. Return Value: DWORD value. --*/ { ULONG_PTR Value = 0; ULONG_PTR Add; _wcslwr((LPWSTR)psz); while( isspace( *psz )){ ++psz; } for( ;; ++psz ){ if( *psz >= TEXT( '0' ) && *psz <= TEXT( '9' )){ Add = *psz - TEXT( '0' ); } else if( *psz >= TEXT( 'a' ) && *psz <= TEXT( 'f' )){ Add = *psz - TEXT( 'a' ) + 0xa; } else { break; } Value *= 0x10; Value += Add; } return Value; } BOOL ValidateSpoolHandle( PSPOOL pSpool, DWORD dwDisallowMask ) { BOOL ReturnValue; try { // // Zombied handles should return back error. The client // side will see ERROR_INVALID_HANDLE, close it and revalidate. // if (( pSpool == NULL ) || ( pSpool == INVALID_HANDLE_VALUE ) || ( pSpool->Status & SPOOL_STATUS_ZOMBIE ) || ( pSpool->signature != SJ_SIGNATURE ) || ( pSpool->TypeofHandle & dwDisallowMask ) || ( pSpool->TypeofHandle & PRINTER_HANDLE_XCV_PORT ) || ( pSpool->pIniSpooler->signature != ISP_SIGNATURE ) || ( ( pSpool->TypeofHandle & PRINTER_HANDLE_PRINTER ) && ( pSpool->pIniPrinter->signature !=IP_SIGNATURE ) )) { ReturnValue = FALSE; } else { ReturnValue = TRUE; } }except (1) { ReturnValue = FALSE; } if ( !ReturnValue ) SetLastError( ERROR_INVALID_HANDLE ); return ReturnValue; } BOOL UpdateString( LPWSTR* ppszCur, LPWSTR pszNew) { // // !! LATER !! // // Replace with non-nls wcscmp since we want byte comparison and // only care if the strings are different (ignore ordering). // if ((!*ppszCur || !**ppszCur) && (!pszNew || !*pszNew)) return FALSE; if (!*ppszCur || !pszNew || wcscmp(*ppszCur, pszNew)) { ReallocSplStr(ppszCur, pszNew); return TRUE; } return FALSE; } BOOL CreateDirectoryWithoutImpersonatingUser( LPWSTR pDirectory ) /*++ Routine Description: This routine stops impersonating the user and creates a directory Arguments: pDirectory - Fully Qualified path of directory. Return Value: TRUE - Success FALSE - failed ( call GetLastError ) --*/ { HANDLE hToken = INVALID_HANDLE_VALUE; BOOL bReturnValue; SPLASSERT( pDirectory != NULL ); hToken = RevertToPrinterSelf(); bReturnValue = CreateDirectory( pDirectory, NULL ); if ( bReturnValue == FALSE ) { DBGMSG( DBG_WARNING, ("CreateDirectoryWithoutImpersonatingUser failed CreateDirectory %ws error %d\n", pDirectory, GetLastError() )); } if ( hToken != INVALID_HANDLE_VALUE ) { ImpersonatePrinterClient(hToken); } return bReturnValue; } BOOL DeleteAllFilesInDirectory( LPWSTR pDirectory, BOOL bWaitForReboot ) /*++ Routine Description: Deletes all files the specified directory If it can't be deleted it gets marked for deletion on next reboot. Arguments: pDirectory - Fully Qualified path of directory. bWaitForReboot - Don't delete the files until a reboot Return Value: TRUE - Success FALSE - failed something major, like allocating memory. --*/ { BOOL bReturnValue = FALSE; HANDLE hFindFile; WIN32_FIND_DATA FindData; WCHAR ScratchBuffer[ MAX_PATH ]; DBGMSG( DBG_TRACE, ("DeleteAllFilesInDirectory: bWaitForReboot = %\n", bWaitForReboot )); SPLASSERT( pDirectory != NULL ); if (StrNCatBuff(ScratchBuffer, COUNTOF(ScratchBuffer), pDirectory, L"\\*", NULL) != ERROR_SUCCESS) return FALSE; hFindFile = FindFirstFile( ScratchBuffer, &FindData ); if ( hFindFile != INVALID_HANDLE_VALUE ) { do { // // Don't Attempt to Delete Directories // if ( !( FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) { // // Fully Qualified Path // if (StrNCatBuff( ScratchBuffer, COUNTOF(ScratchBuffer), pDirectory, L"\\", FindData.cFileName, NULL) == ERROR_SUCCESS) { if ( bWaitForReboot || !DeleteFile( ScratchBuffer ) ) { DBGMSG( DBG_WARNING, ("DeleteAllFilesInDirectory failed DeleteFile( %ws ) error %d\n", ScratchBuffer, GetLastError() )); if ( !MoveFileEx( ScratchBuffer, NULL, MOVEFILE_DELAY_UNTIL_REBOOT ) ) { DBGMSG( DBG_WARNING, ("DeleteAllFilesInDirectory failed MoveFileEx %ws error %d\n", ScratchBuffer, GetLastError() )); } else { DBGMSG( DBG_TRACE, ("MoveFileEx %ws Delay until reboot OK\n", ScratchBuffer )); } } else { DBGMSG( DBG_TRACE, ("Deleted %ws OK\n", ScratchBuffer )); } } } } while( FindNextFile( hFindFile, &FindData ) ); bReturnValue = FindClose( hFindFile ); } else { DBGMSG( DBG_WARNING, ("DeleteOldDrivers failed findfirst ( %ws ), error %d\n", ScratchBuffer, GetLastError() )); } return bReturnValue; } BOOL DeleteAllFilesAndDirectory( LPWSTR pDirectory, BOOL bWaitForReboot ) /*++ Routine Description: Deletes all files the specified directory, then deletes the directory. If the Directory cannot be deleted right away, it is set to be deleted at reboot time. Security NOTE - This routine runs as SYSTEM, not imperonating the user Arguments: pDirectory - Fully Qualified path of directory. Return Value: TRUE - Success FALSE - failed something major, like allocating memory. --*/ { BOOL bReturnValue; HANDLE hToken = INVALID_HANDLE_VALUE; DBGMSG( DBG_TRACE, ("DeleteAllFilesAndDirectory: bWaitForReboot = %d\n", bWaitForReboot )); hToken = RevertToPrinterSelf(); if( bReturnValue = DeleteAllFilesInDirectory( pDirectory, bWaitForReboot ) ) { if ( bWaitForReboot || !RemoveDirectory( pDirectory )) { if (!SplMoveFileEx( pDirectory, NULL, MOVEFILE_DELAY_UNTIL_REBOOT )) { DBGMSG( DBG_WARNING, ("DeleteAllFilesAndDirectory failed to delete %ws until reboot %d\n", pDirectory, GetLastError() )); } else { DBGMSG( DBG_TRACE, ( "DeleteAllFilesAndDirectory: MoveFileEx Delay until reboot OK\n" )); } } else { DBGMSG( DBG_TRACE, ("DeleteAllFilesAndDirectory deleted %ws OK\n", pDirectory )); } } if ( hToken != INVALID_HANDLE_VALUE ) { ImpersonatePrinterClient(hToken); } return bReturnValue; } VOID DeleteDirectoryRecursively( LPCWSTR pszDirectory, BOOL bWaitForReboot ) /*++ Routine Name: DeleteDirectoryRecursively Routine Description: Recursively Deletes the specified directory If it can't be deleted it gets marked for deletion on next reboot. Arguments: pDirectory - Fully Qualified path of directory. bWaitForReboot - Don't delete the files until a reboot Return Value: Nothing. --*/ { HANDLE hFindFile; WIN32_FIND_DATA FindData; WCHAR ScratchBuffer[ MAX_PATH ]; if ( pszDirectory && StrNCatBuff(ScratchBuffer, COUNTOF(ScratchBuffer), pszDirectory, L"\\*", NULL) == ERROR_SUCCESS ) { hFindFile = FindFirstFile(ScratchBuffer, &FindData); if ( hFindFile != INVALID_HANDLE_VALUE ) { do { // // Don't delete current and parent directory. // if (wcscmp(FindData.cFileName, L".") != 0 && wcscmp(FindData.cFileName, L"..") != 0 && StrNCatBuff( ScratchBuffer, COUNTOF(ScratchBuffer), pszDirectory, L"\\", FindData.cFileName, NULL) == ERROR_SUCCESS) { if (!(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { if (bWaitForReboot || !DeleteFile(ScratchBuffer)) { // // Delete the file on reboot if asked or if deletion failed. // SplMoveFileEx(ScratchBuffer, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); } } else { // // Delete subdirectory // DeleteAllFilesAndDirectory(ScratchBuffer, bWaitForReboot); } } } while (FindNextFile(hFindFile, &FindData)); FindClose(hFindFile); if (bWaitForReboot || !RemoveDirectory(pszDirectory)) { // // Delete the directory on reboot if asked or if deletion failed. // SplMoveFileEx(pszDirectory, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); } } } return; } DWORD CreateNumberedTempDirectory( IN LPCWSTR pszDirectory, OUT LPWSTR *ppszTempDirectory ) /*++ Routine Name: CreateNumberedTempDirectory Routine Description: Creates a temporary subdirectory named 1... 500 The lenght of ppTempDirectory cannot be bigger than MAX_PATH Returns the number of directory created or -1 for failure Arguments: pszDirectory - directory where to created temporary ppszTempDirectory - path of the new temporary directory Return Value: If success, returns the number of directory created. Returns -1 if a failure occurs. --*/ { DWORD dwIndex, dwTempDir; WCHAR szTempDir[4]; WCHAR *pszTemporary = NULL; dwTempDir = -1; if (pszDirectory && ppszTempDirectory) { *ppszTempDirectory = NULL; if (pszTemporary = AllocSplMem((wcslen(pszDirectory) + COUNTOF(szTempDir) + 1) * sizeof(WCHAR))) { for (dwIndex = 1; dwIndex < 500; ++dwIndex) { _itow(dwIndex, szTempDir, 10); if (StrNCatBuff(pszTemporary, MAX_PATH, pszDirectory, L"\\", szTempDir, NULL) == ERROR_SUCCESS && !DirectoryExists(pszTemporary) && CreateDirectory(pszTemporary, NULL)) { dwTempDir = dwIndex; break; } } } } if (dwTempDir != -1) { *ppszTempDirectory = pszTemporary; } else { SetLastError(ERROR_NO_SYSTEM_RESOURCES); FreeSplMem(pszTemporary); } return dwTempDir; } // // Dependent file fields are LPWSTR field of filenames separated by \0 // and terminated by \0\0 // 2 such fields are same if same set of filenames appear // (order of filenames does not matter) // BOOL SameMultiSz( LPWSTR pszz1, LPWSTR pszz2 ) { LPWSTR psz1, psz2; if ( !pszz1 && !pszz2 ) return TRUE; if ( !pszz1 || !pszz2 ) return FALSE; // // Check there are same number of strings // for ( psz1 = pszz1, psz2 = pszz2 ; *psz1 && *psz2 ; psz1 += wcslen(psz1)+1, psz2 += wcslen(psz2)+1 ) ; // // If different number of strings return FALSE // if ( *psz1 || *psz2 ) return FALSE; // // Check in pszz2 for each string in pszz1 // for ( psz1 = pszz1 ; *psz1 ; psz1 += wcslen(psz1) + 1 ) { for ( psz2 = pszz2 ; *psz2 && _wcsicmp(psz1, psz2) ; psz2 += wcslen(psz2) + 1 ) ; // // Did we find psz1 in pszz2 // if ( ! *psz2 ) { return FALSE; } } return TRUE; } int wstrcmpEx( LPCWSTR s1, LPCWSTR s2, BOOL bCaseSensitive ) { if ( s1 && *s1 ) { if ( s2 && *s2 ) { return bCaseSensitive ? wcscmp(s1, s2) : _wcsicmp(s1, s2); } else { return 1; } } else { if ( s2 && *s2 ) { return -1; } else { return 0; } } } BOOL RegSetString( HANDLE hKey, LPWSTR pValueName, LPWSTR pStringValue, PDWORD pdwLastError, PINISPOOLER pIniSpooler ) { BOOL bReturnValue; LPWSTR pString; DWORD cbString; DWORD Status; if ( pStringValue ) { pString = pStringValue; cbString = ( wcslen( pStringValue ) + 1 )*sizeof(WCHAR); } else { pString = szNull; cbString = sizeof(WCHAR); } Status = SplRegSetValue( hKey, pValueName, REG_SZ, (LPBYTE)pString, cbString, pIniSpooler ); if ( Status != ERROR_SUCCESS ) { DBGMSG( DBG_WARNING, ("RegSetString value %ws string %ws error %d\n", pValueName, pString, Status )); *pdwLastError = Status; bReturnValue = FALSE; } else { bReturnValue = TRUE; } return bReturnValue; } BOOL RegSetDWord( HANDLE hKey, LPWSTR pValueName, DWORD dwParam, PDWORD pdwLastError, PINISPOOLER pIniSpooler ) { BOOL bReturnValue; LPWSTR pString; DWORD Status; Status = SplRegSetValue( hKey, pValueName, REG_DWORD, (LPBYTE)&dwParam, sizeof(DWORD), pIniSpooler ); if ( Status != ERROR_SUCCESS ) { DBGMSG( DBG_WARNING, ("RegSetDWord value %ws DWORD %x error %d\n", pValueName, dwParam, Status )); *pdwLastError = Status; bReturnValue = FALSE; } else { bReturnValue = TRUE; } return bReturnValue; } BOOL RegSetBinaryData( HKEY hKey, LPWSTR pValueName, LPBYTE pData, DWORD cbData, PDWORD pdwLastError, PINISPOOLER pIniSpooler ) { DWORD Status; BOOL bReturnValue; Status = SplRegSetValue( hKey, pValueName, REG_BINARY, pData, cbData, pIniSpooler ); if ( Status != ERROR_SUCCESS ) { DBGMSG( DBG_WARNING, ("RegSetBinaryData Value %ws pData %x cbData %d error %d\n", pValueName, pData, cbData, Status )); bReturnValue = FALSE; *pdwLastError = Status; } else { bReturnValue = TRUE; } return bReturnValue; } BOOL RegSetMultiString( HANDLE hKey, LPWSTR pValueName, LPWSTR pStringValue, DWORD cchString, PDWORD pdwLastError, PINISPOOLER pIniSpooler ) { BOOL bReturnValue; DWORD Status; LPWSTR pString; WCHAR szzNull[2]; if ( pStringValue ) { pString = pStringValue; cchString *= sizeof(WCHAR); } else { szzNull[0] = szzNull[1] = '\0'; pString = szNull; cchString = 2 * sizeof(WCHAR); } Status = SplRegSetValue( hKey, pValueName, REG_MULTI_SZ, (LPBYTE)pString, cchString, pIniSpooler ); if ( Status != ERROR_SUCCESS ) { DBGMSG( DBG_WARNING, ("RegSetMultiString value %ws string %ws error %d\n", pValueName, pString, Status )); *pdwLastError = Status; bReturnValue = FALSE; } else { bReturnValue = TRUE; } return bReturnValue; } BOOL RegGetString( HANDLE hKey, LPWSTR pValueName, LPWSTR *ppValue, LPDWORD pcchValue, PDWORD pdwLastError, BOOL bFailIfNotFound, PINISPOOLER pIniSpooler ) /*++ Routine Description: Allocates memory and reads a value from Registry for a value which was earlier set by calling RegSetValueEx. Arguments: hKey : currently open key to be used to query the registry pValueName : value to be used to query the registry ppValue : on return value of TRUE *ppValue (memory allocated by the routine) will have the value pdwLastError : on failure *dwLastError will give the error bFailIfNotFound : Tells if the field is mandatory (if not found error) Return Value: TRUE : value is found and succesfully read. Memory will be allocated to hold the value FALSE: Value was not read. If bFailIfNotFound was TRUE error code will be set. History: Written by MuhuntS (Muhunthan Sivapragasam)June 95 --*/ { BOOL bReturnValue = TRUE; LPWSTR pString; DWORD cbValue; DWORD Status, Type; // // First query to find out size // cbValue = 0; Status = SplRegQueryValue( hKey, pValueName, &Type, NULL, &cbValue, pIniSpooler ); if ( Status != ERROR_SUCCESS ) { // Set error code only if it is a required field if ( bFailIfNotFound ) *pdwLastError = Status; bReturnValue = FALSE; } else if ( (Type == REG_SZ && cbValue > sizeof(WCHAR) ) || (Type == REG_MULTI_SZ && cbValue > 2*sizeof(WCHAR)) ) { // // Something (besides \0 or \0\0) to read // if ( !(*ppValue=AllocSplMem(cbValue) ) ) { *pdwLastError = GetLastError(); bReturnValue = FALSE; } else { Status = SplRegQueryValue( hKey, pValueName, &Type, (LPBYTE)*ppValue, &cbValue, pIniSpooler ); if ( Status != ERROR_SUCCESS ) { DBGMSG( DBG_WARNING, ("RegGetString value %ws string %ws error %d\n", pValueName, **ppValue, Status )); *pdwLastError = Status; bReturnValue = FALSE; } else { *pcchValue = cbValue / sizeof(WCHAR); bReturnValue = TRUE; } } } return bReturnValue; } BOOL RegGetMultiSzString( HANDLE hKey, LPWSTR pValueName, LPWSTR *ppValue, LPDWORD pcchValue, PDWORD pdwLastError, BOOL bFailIfNotFound, PINISPOOLER pIniSpooler ) /*++ Routine Description: Duplicate function for RegGetString. Handles multi-sz strings so that Spooler doesn't crash. Arguments: hKey : currently open key to be used to query the registry pValueName : value to be used to query the registry ppValue : on return value of TRUE *ppValue (memory allocated by the routine) will have the value pdwLastError : on failure *dwLastError will give the error bFailIfNotFound : Tells if the field is mandatory (if not found error) Return Value: TRUE : value is found and succesfully read. Memory will be allocated to hold the value FALSE: Value was not read. If bFailIfNotFound was TRUE error code will be set. History: Written by AdinaTru. This function is a fix for the case when 3rd party applications install drivers by writing registry string values instead of multi-sz. This causes Spooler to AV because it will handle a string as a multi-sz string. The goal of having this function was to provide a quick fix/low regression risk for XP RC2 release. A bug was opened for rewriting RegGetMultiSzString and RegGetString in BlackComb timeframe. --*/ { BOOL bReturnValue = TRUE; LPWSTR pString; DWORD cbValue; DWORD Status, Type; // // First query to find out size // cbValue = 0; Status = SplRegQueryValue( hKey, pValueName, &Type, NULL, &cbValue, pIniSpooler ); if ( Status != ERROR_SUCCESS ) { // Set error code only if it is a required field if ( bFailIfNotFound ) *pdwLastError = Status; bReturnValue = FALSE; } else if ( (Type == REG_SZ && cbValue > sizeof(WCHAR) ) || (Type == REG_MULTI_SZ && cbValue > 2*sizeof(WCHAR)) ) { // // Something (besides \0 or \0\0) to read // // // We expect a REG_MULTI_SZ string. Add an extra zero so Spooler doesn't crash. // XP RC2 fix. // if (Type == REG_SZ) { cbValue += sizeof(WCHAR); } if ( !(*ppValue=AllocSplMem(cbValue) ) ) { *pdwLastError = GetLastError(); bReturnValue = FALSE; } else { Status = SplRegQueryValue( hKey, pValueName, &Type, (LPBYTE)*ppValue, &cbValue, pIniSpooler ); if ( Status != ERROR_SUCCESS ) { DBGMSG( DBG_WARNING, ("RegGetString value %ws string %ws error %d\n", pValueName, **ppValue, Status )); *pdwLastError = Status; bReturnValue = FALSE; // // Caller will must the memory regardless of success or failure. // } else { *pcchValue = cbValue / sizeof(WCHAR); bReturnValue = TRUE; } } } return bReturnValue; } VOID FreeStructurePointers( LPBYTE lpStruct, LPBYTE lpStruct2, LPDWORD lpOffsets) /*++ Routine Description: This routine frees memory allocated to all the pointers in the structure If lpStruct2 is specified only pointers in lpStruct which are different than the ones in lpStruct will be freed Arguments: lpStruct: Pointer to the structure lpStruct2: Pointer to the structure to compare with (optional) lpOffsets: An array of DWORDS (terminated by -1) givings offsets in the structure which have memory which needs to be freed Return Value: nothing History: MuhuntS -- Aug 95 --*/ { register INT i; if ( lpStruct2 ) { for( i=0; lpOffsets[i] != 0xFFFFFFFF; ++i ) { if ( *(LPBYTE *) (lpStruct+lpOffsets[i]) && *(LPBYTE *) (lpStruct+lpOffsets[i]) != *(LPBYTE *) (lpStruct2+lpOffsets[i]) ) FreeSplMem(*(LPBYTE *) (lpStruct+lpOffsets[i])); } } else { for( i=0; lpOffsets[i] != 0xFFFFFFFF; ++i ) { if ( *(LPBYTE *) (lpStruct+lpOffsets[i]) ) FreeSplMem(*(LPBYTE *) (lpStruct+lpOffsets[i])); } } } /*++ Routine Name: AllocOrUpdateStringAndTestSame Routine Description: This routine can be used to do an atomic update of values in a structure. Create a temporary structure and copy the old structure to it. Then call this routine for all LPWSTR fields to check and update strings If the value changes: This routine will allocate memory and assign pointer in the temporary structure. Arguments: ppString : Points to a pointer in the temporary sturucture pNewValue : New value to be set pOldValue : Value in the original strucutre bCaseSensitive : Determines whether case-sensitive string compare is performed pbFail : On error set this to TRUE (Note: it could already be TRUE) *pbIdentical : If the strings are diferent this is set to FALSE. (Could already be false). Return Value: TRUE on success, else FALSE --*/ BOOL AllocOrUpdateStringAndTestSame( IN LPWSTR *ppString, IN LPCWSTR pNewValue, IN LPCWSTR pOldValue, IN BOOL bCaseSensitive, IN OUT BOOL *pbFail, IN OUT BOOL *pbIdentical ) { BOOL bReturn = TRUE; int iReturn; if ( *pbFail ) return FALSE; if (wstrcmpEx(pNewValue, pOldValue, bCaseSensitive)) { *pbIdentical = FALSE; if ( pNewValue && *pNewValue ) { if ( !(*ppString = AllocSplStr(pNewValue)) ) { *pbFail = TRUE; bReturn = FALSE; } } else { *ppString = NULL; } } return bReturn; } /*++ Routine Name: AllocOrUpdateString Routine Description: This routine can be used to do an atomic update of values in a structure. Create a temporary structure and copy the old structure to it. Then call this routine for all LPWSTR fields to check and update strings If the value changes: This routine will allocate memory and assign pointer in the temporary structure. Arguments: ppString : Points to a pointer in the temporary sturucture pNewValue : New value to be set pOldValue : Value in the original strucutre bCaseSensitive : Determines whether case-sensitive string compare is performed pbFail : On error set this to TRUE (Note: it could already be TRUE) Return Value: TRUE on success, else FALSE --*/ BOOL AllocOrUpdateString( IN LPWSTR *ppString, IN LPCWSTR pNewValue, IN LPCWSTR pOldValue, IN BOOL bCaseSensitive, IN OUT BOOL *pbFail ) { BOOL bIdentical = FALSE; return AllocOrUpdateStringAndTestSame(ppString, pNewValue, pOldValue, bCaseSensitive, pbFail, &bIdentical); } VOID CopyNewOffsets( LPBYTE pStruct, LPBYTE pTempStruct, LPDWORD lpOffsets) /*++ Routine Description: This routine can be used to do an atomic update of values in a structure. Create a temporary structure and allocate memory for values which are being updated in it, and set the remaining pointers to those in the original. This routine is called at the end to update the structure. Arguments: pStruct: Pointer to the structure pTempStruct: Pointer to the temporary structure lpOffsets: An array of DWORDS givings offsets within the stuctures Return Value: nothing History: MuhuntS -- Aug 95 --*/ { register INT i; for( i=0; lpOffsets[i] != 0xFFFFFFFF; ++i ) { if ( *(LPBYTE *) (pStruct+lpOffsets[i]) != *(LPBYTE *) (pTempStruct+lpOffsets[i]) ) { if ( *(LPBYTE *) (pStruct+lpOffsets[i]) ) FreeSplMem(*(LPBYTE *) (pStruct+lpOffsets[i])); *(LPBYTE *) (pStruct+lpOffsets[i]) = *(LPBYTE *) (pTempStruct+lpOffsets[i]); } } } DWORD GetIniDriverAndDirForThisMachine( IN PINIPRINTER pIniPrinter, OUT LPWSTR pszDriverDir, OUT PINIDRIVER *ppIniDriver ) /*++ Description: Gets the path to the driver directory for the printer on the local machine Arguments: pIniPrinter - Points to IniPrinter pszDriverDir - A buffer of size MAX_PATH to get the directory path Return Vlaue: Number of characters copied (0 on failure) --*/ { PINIVERSION pIniVersion = NULL; PINIENVIRONMENT pIniEnvironment; PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler; DWORD dwIndex; EnterSplSem(); // // Find driver file for the given driver and then get it's fullpath // SPLASSERT(pIniPrinter && pIniPrinter->pIniDriver && pIniPrinter->pIniDriver->pName); pIniEnvironment = FindEnvironment(szEnvironment, pIniSpooler); *ppIniDriver = FindCompatibleDriver(pIniEnvironment, &pIniVersion, pIniPrinter->pIniDriver->pName, dwMajorVersion, dwUpgradeFlag); SPLASSERT(*ppIniDriver); dwIndex = GetDriverVersionDirectory(pszDriverDir, MAX_PATH - 2, pIniPrinter->pIniSpooler, pIniEnvironment, pIniVersion, *ppIniDriver, NULL); pszDriverDir[dwIndex++] = L'\\'; pszDriverDir[dwIndex] = L'\0'; LeaveSplSem(); return dwIndex; } LPWSTR GetConfigFilePath( IN PINIPRINTER pIniPrinter ) /*++ Description: Gets the full path to the config file (driver ui file) associated with the driver. Memory is allocated Arguments: pIniPrinter - Points to IniPrinter Return Vlaue: Pointer to the printer name buffer (NULL on error) --*/ { DWORD dwIndex; WCHAR szDriverPath[MAX_PATH + 1]; PINIDRIVER pIniDriver; if ( dwIndex = GetIniDriverAndDirForThisMachine(pIniPrinter, szDriverPath, &pIniDriver) ) wcscpy(szDriverPath+dwIndex, pIniDriver->pConfigFile); return AllocSplStr(szDriverPath); } PDEVMODE ConvertDevModeToSpecifiedVersion( IN PINIPRINTER pIniPrinter, IN PDEVMODE pDevMode, IN LPWSTR pszConfigFile, OPTIONAL IN LPWSTR pszPrinterNameWithToken, OPTIONAL IN BOOL bNt35xVersion ) /*++ Description: Calls driver UI routines to get the default devmode and then converts given devmode to that version. If the input devmode is in IniPrinter routine makes a copy before converting it. This routine needs to be called from inside spooler semaphore Arguments: pIniPrinter - Points to IniPrinter pDevMode - Devmode to convert to current version pConfigFile - Full path to driver UI file to do LoadLibrary (optional) pszPrinterNameWithToken - Name of printer with token (optional) bToNt3xVersion - If TRUE devmode is converted to Nt3x format, else to current version Return Vlaue: Pointer to new devmode on success, NULL on failure --*/ { LPWSTR pszLocalConfigFile = pszConfigFile; LPWSTR pszLocalPrinterNameWithToken = pszPrinterNameWithToken; LPDEVMODE pNewDevMode = NULL, pOldDevMode = NULL; DWORD dwNeeded, dwLastError; LONG lNeeded; HANDLE hDevModeConvert = NULL,hPrinter = NULL; BOOL bCallDocumentProperties = FALSE; SplInSem(); // // If ConfigFile or PrinterNameWithToken is not given allocate it locally // if ( !pszLocalConfigFile ) { pszLocalConfigFile = GetConfigFilePath(pIniPrinter); if ( !pszLocalConfigFile ) goto Cleanup; } if ( !pszLocalPrinterNameWithToken ) { pszLocalPrinterNameWithToken = pszGetPrinterName( pIniPrinter, TRUE, pszLocalOnlyToken ); if ( !pszLocalPrinterNameWithToken ) goto Cleanup; } // // If we are trying to convert pIniPrinter->pDevMode make a copy since we are going to leave SplSem // if ( pDevMode ) { if ( pDevMode == pIniPrinter->pDevMode ) { dwNeeded = pDevMode->dmSize + pDevMode->dmDriverExtra; SPLASSERT(dwNeeded == pIniPrinter->cbDevMode); pOldDevMode = AllocSplMem(dwNeeded); if ( !pOldDevMode ) goto Cleanup; CopyMemory((LPBYTE)pOldDevMode, (LPBYTE)pDevMode, dwNeeded); } else { pOldDevMode = pDevMode; } } // // Driver is going to call OpenPrinter, so leave SplSem // LeaveSplSem(); SplOutSem(); hDevModeConvert = LoadDriverFiletoConvertDevmode(pszLocalConfigFile); if ( !hDevModeConvert ) { // If the function is not exported and 3.5x conversion is not required // the devmode can be got from DocumentProperties if ( bNt35xVersion != NT3X_VERSION ) { bCallDocumentProperties = TRUE; } goto CleanupFromOutsideSplSem; } dwNeeded = 0; if ( bNt35xVersion == NT3X_VERSION ) { // // Call CallDrvDevModeConversion to allocate memory and return 351 devmode // dwLastError = CallDrvDevModeConversion(hDevModeConvert, pszLocalPrinterNameWithToken, (LPBYTE)pOldDevMode, (LPBYTE *)&pNewDevMode, &dwNeeded, CDM_CONVERT351, TRUE); SPLASSERT(dwLastError == ERROR_SUCCESS || !pNewDevMode); } else { // // Call CallDrvDevModeConversion to allocate memory and give default devmode dwLastError = CallDrvDevModeConversion(hDevModeConvert, pszLocalPrinterNameWithToken, NULL, (LPBYTE *)&pNewDevMode, &dwNeeded, CDM_DRIVER_DEFAULT, TRUE); if ( dwLastError != ERROR_SUCCESS ) { SPLASSERT(!pNewDevMode); // Call DocumentProperties to get the default devmode bCallDocumentProperties = TRUE; goto CleanupFromOutsideSplSem; } // // If we have an input devmode to convert to current mode call driver again // if ( pOldDevMode ) { dwLastError = CallDrvDevModeConversion(hDevModeConvert, pszLocalPrinterNameWithToken, (LPBYTE)pOldDevMode, (LPBYTE *)&pNewDevMode, &dwNeeded, CDM_CONVERT, FALSE); // // If call failed free devmode which was allocated by previous call // if ( dwLastError != ERROR_SUCCESS ) { // Call DocumentProperties to get the default devmode bCallDocumentProperties = TRUE; goto CleanupFromOutsideSplSem; } } } CleanupFromOutsideSplSem: if (bCallDocumentProperties) { // Get a client side printer handle to pass to the driver if (!(* pfnOpenPrinter)(pszLocalPrinterNameWithToken, &hPrinter, NULL)) { goto ReEnterSplSem; } if (!pNewDevMode) { // Get the default devmode lNeeded = (* pfnDocumentProperties)(NULL, hPrinter, pszLocalPrinterNameWithToken, NULL, NULL, 0); if (lNeeded <= 0 || !(pNewDevMode = (LPDEVMODEW) AllocSplMem(lNeeded)) || (* pfnDocumentProperties)(NULL, hPrinter, pszLocalPrinterNameWithToken, pNewDevMode, NULL, DM_OUT_BUFFER) < 0) { if (pNewDevMode) { FreeSplMem(pNewDevMode); pNewDevMode = NULL; goto ReEnterSplSem; } } } if (pOldDevMode) { // Convert to Current mode if ((* pfnDocumentProperties)(NULL, hPrinter, pszLocalPrinterNameWithToken, pNewDevMode, pOldDevMode, DM_IN_BUFFER | DM_OUT_BUFFER) < 0) { FreeSplMem(pNewDevMode); pNewDevMode = NULL; goto ReEnterSplSem; } } } ReEnterSplSem: if (hPrinter) { (* pfnClosePrinter)(hPrinter); } SplOutSem(); EnterSplSem(); Cleanup: if ( hDevModeConvert ) UnloadDriverFile(hDevModeConvert); if ( pszLocalConfigFile != pszConfigFile ) FreeSplStr(pszLocalConfigFile); if ( pszPrinterNameWithToken != pszLocalPrinterNameWithToken ) FreeSplStr(pszLocalPrinterNameWithToken); if ( pOldDevMode != pDevMode ) FreeSplMem(pOldDevMode); return pNewDevMode; } BOOL IsPortType( LPWSTR pPort, LPWSTR pPrefix ) { DWORD dwLen; SPLASSERT(pPort && *pPort && pPrefix && *pPrefix); dwLen = wcslen(pPrefix); if ( wcslen(pPort) < dwLen ) { return FALSE; } if ( _wcsnicmp(pPort, pPrefix, dwLen) ) { return FALSE; } // // wcslen guarenteed >= 3 // return pPort[ wcslen( pPort ) - 1 ] == L':'; } /* UnicodeToAnsiString * * Parameters: * * pUnicode - A valid source Unicode string. * * pANSI - A pointer to a buffer large enough to accommodate * the converted string. * * StringLength - The length of the source Unicode string. * If 0 (NULL_TERMINATED), the string is assumed to be * null-terminated. * * * Notes: * With DBCS enabled, we will allocate twice the size of the * buffer including the null terminator to take care of double * byte character strings - KrishnaG * * pUnicode is truncated to StringLength characters. * * Return: * * The return value from WideCharToMultiByte, the number of * multi-byte characters returned. * * * andrewbe, 11 Jan 1993 */ INT UnicodeToAnsiString( LPWSTR pUnicode, LPSTR pAnsi, DWORD StringLength) { LPSTR pTempBuf = NULL; INT rc = 0; if( StringLength == NULL_TERMINATED ) { // // StringLength is just the // number of characters in the string // StringLength = wcslen( pUnicode ); } // // WideCharToMultiByte doesn't NULL terminate if we're copying // just part of the string, so terminate here. // pUnicode[StringLength] = 0; // // Include one for the NULL // StringLength++; // // Unfortunately, WideCharToMultiByte doesn't do conversion in place, // so allocate a temporary buffer, which we can then copy: // if( pAnsi == (LPSTR)pUnicode ) { pTempBuf = AllocSplMem(StringLength*2); pAnsi = pTempBuf; } if( pAnsi ) { rc = WideCharToMultiByte( CP_THREAD_ACP, 0, pUnicode, StringLength, pAnsi, StringLength*2, NULL, NULL ); } /* If pTempBuf is non-null, we must copy the resulting string * so that it looks as if we did it in place: */ if( pTempBuf ) { if( rc > 0 ) { pAnsi = (LPSTR)pUnicode; strcpy( pAnsi, pTempBuf ); } FreeSplMem(pTempBuf); } return rc; } LPWSTR AnsiToUnicodeStringWithAlloc( LPSTR pAnsi ) /*++ Description: Convert ANSI string to UNICODE. Routine allocates memory from the heap which should be freed by the caller. Arguments: pAnsi - Points to the ANSI string Return Vlaue: Pointer to UNICODE string --*/ { LPWSTR pUnicode; DWORD rc; rc = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pAnsi, -1, NULL, 0); rc *= sizeof(WCHAR); if ( !rc || !(pUnicode = (LPWSTR) AllocSplMem(rc)) ) return NULL; rc = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pAnsi, -1, pUnicode, rc); if ( rc ) return pUnicode; else { FreeSplMem(pUnicode); return NULL; } } VOID FreeIniSpoolerOtherNames( PINISPOOLER pIniSpooler ) { DWORD Index = 0; if ( pIniSpooler->ppszOtherNames ) { for ( Index = 0 ; Index < pIniSpooler->cOtherNames ; ++Index ) { FreeSplMem(pIniSpooler->ppszOtherNames[Index]); } FreeSplMem(pIniSpooler->ppszOtherNames); } pIniSpooler->cOtherNames = 0; } BOOL SplMonitorIsInstalled( LPWSTR pMonitorName ) { BOOL bRet; EnterSplSem(); bRet = FindMonitor(pMonitorName, pLocalIniSpooler) != NULL; LeaveSplSem(); return bRet; } BOOL PrinterDriverEvent( PINIPRINTER pIniPrinter, INT PrinterEvent, LPARAM lParam ) /*++ --*/ { BOOL ReturnValue = FALSE; LPWSTR pPrinterName = NULL; BOOL InSpoolSem = TRUE; SplOutSem(); EnterSplSem(); // // We have to Clone the name string, incase someone does a // rename whilst outside criticalsection. // pPrinterName = pszGetPrinterName( pIniPrinter, TRUE, pszLocalsplOnlyToken); LeaveSplSem(); SplOutSem(); if ( (pIniPrinter->pIniSpooler->SpoolerFlags & SPL_PRINTER_DRIVER_EVENT) && pPrinterName != NULL ) { ReturnValue = SplDriverEvent( pPrinterName, PrinterEvent, lParam ); } FreeSplStr( pPrinterName ); return ReturnValue; } BOOL SplDriverEvent( LPWSTR pName, INT PrinterEvent, LPARAM lParam ) { BOOL ReturnValue = FALSE; if ( pfnPrinterEvent != NULL ) { SplOutSem(); SPLASSERT( pName && PrinterEvent ); DBGMSG(DBG_INFO, ("SplDriverEvent %ws %d %x\n", pName, PrinterEvent, lParam)); ReturnValue = (*pfnPrinterEvent)( pName, PrinterEvent, PRINTER_EVENT_FLAG_NO_UI, lParam ); } return ReturnValue; } DWORD OpenPrinterKey( PINIPRINTER pIniPrinter, REGSAM samDesired, HANDLE *phKey, LPCWSTR pKeyName, BOOL bOpen ) /*++ Description: OpenPrinterKey Opens "Printers" and IniPrinter->pName keys, then opens specified subkey "pKeyName" if pKeyName is not NULL This routine needs to be called from inside spooler semaphore Arguments: pIniPrinter - Points to IniPrinter *phPrinterRootKey - Handle to "Printers" key on return *phPrinterKey - Handle to IniPrinter->pName key on return *hKey - Handle to pKeyName key on return pKeyName - Points to SubKey to open Return Value: Success or failure status Author: Steve Wilson (NT) --*/ { LPWSTR pThisKeyName = NULL; LPWSTR pPrinterKeyName = NULL; DWORD rc; PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler; SplInSem(); if (!(pPrinterKeyName = SubChar(pIniPrinter->pName, L'\\', L','))) { rc = GetLastError(); goto error; } if (pKeyName && *pKeyName){ pThisKeyName = AllocSplMem(( wcslen(pPrinterKeyName) + wcslen(pKeyName) + 2 ) * sizeof(WCHAR) ); if (!pThisKeyName) { rc = GetLastError(); goto error; } wsprintf(pThisKeyName, L"%ws\\%ws", pPrinterKeyName, pKeyName); } else { pThisKeyName = pPrinterKeyName; } if (bOpen) { // Open rc = SplRegOpenKey( pIniSpooler->hckPrinters, pThisKeyName, samDesired, phKey, pIniSpooler ); } else { // Create rc = SplRegCreateKey( pIniSpooler->hckPrinters, pThisKeyName, 0, samDesired, NULL, phKey, NULL, pIniSpooler ); } error: if (pThisKeyName != pPrinterKeyName) { FreeSplMem(pThisKeyName); } FreeSplStr(pPrinterKeyName); return rc; } BOOL SplGetDriverDir( HANDLE hIniSpooler, LPWSTR pszDir, LPDWORD pcchDir ) { DWORD dwSize; PINISPOOLER pIniSpooler = (PINISPOOLER)hIniSpooler; SPLASSERT(pIniSpooler && pIniSpooler->signature == ISP_SIGNATURE); dwSize = *pcchDir; *pcchDir = wcslen(pIniSpooler->pDir) + wcslen(szDriverDir) + 2; if ( *pcchDir > dwSize ) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } wsprintf(pszDir, L"%ws\\%ws", pIniSpooler->pDir, szDriverDir); return TRUE; } VOID GetRegistryLocation( IN HANDLE hKey, IN LPCWSTR pszPath, OUT PHANDLE phKeyOut, OUT LPCWSTR *ppszPathOut ) /*++ Routine Description: Take a registry path and detect whether it should be absolute (rooted from HKEY_LOCAL_MACHINE), or if it should be branched off of the subkey that is passed in hKey. By convention, if it starts with "\" then it is an absolute path. Otherwise it is relative off hKey. Arguments: hKey - Print hKey pszPath - Path to parse. If the pszPath starts with a backslash, then it is absolute. phKeyOut - New key that should be used. ppszPathOut - New path that should be used. Return Value: --*/ { if( pszPath && ( pszPath[0] == TEXT( '\\' ))){ *phKeyOut = HKEY_LOCAL_MACHINE; *ppszPathOut = &pszPath[1]; return; } *phKeyOut = hKey; *ppszPathOut = pszPath; } DWORD DeletePrinterSubkey( PINIPRINTER pIniPrinter, PWSTR pKeyName ) { HKEY hKey, hPrinterKey; DWORD dwError; dwError = OpenPrinterKey(pIniPrinter, KEY_WRITE | KEY_READ, &hPrinterKey, NULL, TRUE); if (dwError == ERROR_SUCCESS) { dwError = OpenPrinterKey( pIniPrinter, KEY_WRITE | KEY_READ, &hKey, pKeyName, TRUE); if (dwError == ERROR_SUCCESS) { dwError = SplDeleteThisKey( hPrinterKey, hKey, pKeyName, FALSE, pIniPrinter->pIniSpooler); } SplRegCloseKey(hPrinterKey, pIniPrinter->pIniSpooler); } return dwError; } PWSTR FixDelim( PCWSTR pszInBuffer, WCHAR wcDelim ) /*++ Routine Description: Removes duplicate delimiters from a set of delimited strings Arguments: pszInBuffer - Input list of comma delimited strings wcDelim - The delimit character Return Value: Returns a fixed string --*/ { PWSTR pszIn, pszOut, pszOutBuffer; BOOL bFoundDelim = TRUE; pszOutBuffer = (PWSTR) AllocSplMem((wcslen(pszInBuffer) + 1)*sizeof(WCHAR)); if (pszOutBuffer) { for(pszOut = pszOutBuffer, pszIn = (PWSTR) pszInBuffer ; *pszIn ; ++pszIn) { if (*pszIn == wcDelim) { if (!bFoundDelim) { bFoundDelim = TRUE; *pszOut++ = *pszIn; } } else { bFoundDelim = FALSE; *pszOut++ = *pszIn; } } // Check for trailing delimiter if (pszOut != pszOutBuffer && *(pszOut - 1) == wcDelim) { *(pszOut - 1) = L'\0'; } *pszOut = L'\0'; } return pszOutBuffer; } PWSTR Array2DelimString( PSTRINGS pStringArray, WCHAR wcDelim ) /*++ Routine Description: Converts a PSTRINGS structure to a set of delimited strings Arguments: pStringArray - Input PSTRINGS structure wcDelim - The delimit character Return Value: Delimited string buffer --*/ { DWORD i, nBytes; PWSTR pszDelimString; WCHAR szDelimString[2]; if (!pStringArray || pStringArray->nElements == 0) return NULL; szDelimString[0] = wcDelim; szDelimString[1] = L'\0'; // Determine memory requirement for (i = nBytes = 0 ; i < pStringArray->nElements ; ++i) { // // allocate extra space for the delimiter // if (pStringArray->ppszString[i]) nBytes += (wcslen(pStringArray->ppszString[i]) + 1)*sizeof (WCHAR); } pszDelimString = (PWSTR) AllocSplMem(nBytes); if (!pszDelimString) return NULL; for (i = 0 ; i < pStringArray->nElements - 1 ; ++i) { if (pStringArray->ppszString[i]) { wcscat(pszDelimString, pStringArray->ppszString[i]); wcscat(pszDelimString, szDelimString); } } if (pStringArray->ppszString[i]) wcscat(pszDelimString, pStringArray->ppszString[i]); return pszDelimString; } PSTRINGS ShortNameArray2LongNameArray( PSTRINGS pShortNames ) /*++ Routine Description: Converts a PSTRINGS structure containing short names to a PSTRINGS struct containing the dns equivalents Arguments: pStringArray - Input PSTRINGS structure Return Value: PSTRINGS struct containing the dns equivalents of the input short name PSTRINGS struct. --*/ { PSTRINGS pLongNames; DWORD i; if (!pShortNames) { SetLastError(ERROR_INVALID_PARAMETER); return NULL; } // Allocate LongNameArray pLongNames = AllocStringArray(pShortNames->nElements); if (!pLongNames) return NULL; for (i = 0 ; i < pShortNames->nElements ; ++i) { // GetDNSMachineName may fail, leaving the LongNameArray element empty. This is okay. GetDNSMachineName(pShortNames->ppszString[i], &pLongNames->ppszString[i]); } pLongNames->nElements = pShortNames->nElements; return pLongNames; } PSTRINGS DelimString2Array( PCWSTR pszDelimString, WCHAR wcDelim ) /*++ Routine Description: Converts a delimited string to a PSTRINGS structure Arguments: pszDelimString - Input, delimited strings wcDelim - The delimit character Return Value: PSTRINGS structure --*/ { PWSTR psz, pszDelim; PSTRINGS pStrings = NULL; ULONG i, cChar, nStrings; // Get number of names for (psz = (PWSTR) pszDelimString, nStrings = 0 ; psz++ ; psz = wcschr(psz, wcDelim)) ++nStrings; pStrings = AllocStringArray(nStrings); if (!pStrings) goto error; // Copy delimited string to array for (i = 0, psz = (PWSTR) pszDelimString ; i < nStrings && psz ; ++i, psz = pszDelim + 1) { pszDelim = wcschr(psz, wcDelim); if (pszDelim) { cChar = (ULONG) (pszDelim - psz); pStrings->ppszString[i] = (PWSTR) AllocSplMem((cChar + 1)*sizeof(WCHAR)); } else { pStrings->ppszString[i] = (PWSTR) AllocSplMem((wcslen(psz) + 1)*sizeof(WCHAR)); } if (!pStrings->ppszString[i]) { pStrings->nElements = i; FreeStringArray(pStrings); pStrings = NULL; goto error; } if (pszDelim) { wcsncpy(pStrings->ppszString[i], psz, cChar); *(pStrings->ppszString[i] + cChar) = L'\0'; } else { wcscpy(pStrings->ppszString[i], psz); } } pStrings->nElements = nStrings; error: return pStrings; } VOID FreeStringArray( PSTRINGS pString ) /*++ Routine Description: Frees a PSTRINGS structure Arguments: pString - PSTRINGS structure to free Return Value: --*/ { DWORD i; if (pString) { for (i = 0 ; i < pString->nElements ; ++i) { if (pString->ppszString[i]) FreeSplMem(pString->ppszString[i]); } FreeSplMem(pString); } } PSTRINGS AllocStringArray( DWORD nStrings ) /*++ Routine Description: Allocates a PSTRINGS structure Arguments: nStrings - number of strings in the structure Return Value: pointer to allocated PSTRINGS structure, if any --*/ { PSTRINGS pStrings; // Allocate the STRINGS struct pStrings = (PSTRINGS) AllocSplMem(sizeof(STRINGS) + (nStrings - 1)*sizeof *pStrings->ppszString); return pStrings; } BOOL SplDeleteFile( LPCTSTR lpFileName ) /*++ Routine Name SplDeleteFile Routine Description: Removes SFP protection and Deletes a file. If the file is protected and I fail to remove protection, the user will get warned with a system pop up. Arguments: lpFileName - file full path requested Return Value: DeleteFile's return value --*/ { HANDLE RpcHandle = INVALID_HANDLE_VALUE; RpcHandle = SfcConnectToServer( NULL ); if( RpcHandle != INVALID_HANDLE_VALUE ){ SfcFileException( RpcHandle, (PWSTR)lpFileName, FILE_ACTION_REMOVED ); SfcClose(RpcHandle); } // // SfcFileException might fail with ERROR_FILE_NOT_FOUND because the file is // not in the protected file list.That's why I call DeleteFile anyway. // return DeleteFile( lpFileName ); } BOOL SplMoveFileEx( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, DWORD dwFlags ) /*++ Routine Name SplMoveFileEx Routine Description: Removes SFP protection and move a file; If the file is protected and I fail to remove protection, the user will get warned with a system pop up. Arguments: lpExistingFileName - pointer to the name of the existing file lpNewFileName - pointer to the new name for the file dwFlags - flag that specifies how to move file Return Value: MoveFileEx's return value --*/ { HANDLE RpcHandle = INVALID_HANDLE_VALUE; RpcHandle = SfcConnectToServer( NULL ); if( RpcHandle != INVALID_HANDLE_VALUE ){ SfcFileException( RpcHandle, (PWSTR)lpExistingFileName, FILE_ACTION_REMOVED ); SfcClose(RpcHandle); } // // SfcFileException might fail with ERROR_FILE_NOT_FOUND because the file is // not in the protected file list.That's why I call MoveFileEx anyway. // return MoveFileEx( lpExistingFileName, lpNewFileName, dwFlags ); } BOOL GetSpoolerPolicy( PWSTR pszValue, PBYTE pData, PDWORD pcbData ) { HKEY hPrintPublishKey = NULL; DWORD dwType, dwData, cbData; BOOL bReturn = FALSE; // NOTE: This fcn currently only works if type is REG_DWORD. Expand fcn as needed. cbData = sizeof(DWORD); // Open the registry key if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szPrintPublishPolicy, 0, KEY_READ, &hPrintPublishKey) == ERROR_SUCCESS) { // Query for the policy bits if (RegQueryValueEx(hPrintPublishKey, pszValue, NULL, &dwType, (LPBYTE) pData, pcbData) == ERROR_SUCCESS) bReturn = TRUE; RegCloseKey(hPrintPublishKey); } return bReturn; } DWORD GetDwPolicy( PWSTR pszName, DWORD dwDefault ) { DWORD dwData, cbData = sizeof(DWORD); return GetSpoolerPolicy(pszName, (PBYTE) &dwData, &cbData) ? dwData : dwDefault; } DWORD GetDefaultForKMPrintersBlockedPolicy ( ) { DWORD Default = KM_PRINTERS_ARE_BLOCKED; BOOL bIsNTWorkstation; NT_PRODUCT_TYPE NtProductType; // // DEFAULT_KM_PRINTERS_ARE_BLOCKED is "blocked" // if ( RtlGetNtProductType(&NtProductType) ) { bIsNTWorkstation = NtProductType == NtProductWinNt; Default = bIsNTWorkstation ? WKS_DEFAULT_KM_PRINTERS_ARE_BLOCKED : SERVER_DEFAULT_KM_PRINTERS_ARE_BLOCKED; } return Default; } DWORD GetServerInstallTimeOut( ) { HKEY hKey; DWORD dwDummy; DWORD dwTimeOut = DEFAULT_MAX_TIMEOUT; DWORD dwSize = sizeof(dwTimeOut); LPCWSTR cszPrintKey = L"SYSTEM\\CurrentControlSet\\Control\\Print"; LPCWSTR cszTimeOut = L"ServerInstallTimeOut"; if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, cszPrintKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS ) { if(RegQueryValueEx( hKey, cszTimeOut, 0, &dwDummy, (LPBYTE)&dwTimeOut, &dwSize ) != ERROR_SUCCESS) { dwTimeOut = DEFAULT_MAX_TIMEOUT; } RegCloseKey( hKey ); } return dwTimeOut; } DWORD ReadOverlapped( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead ) { DWORD dwLastError = ERROR_SUCCESS; if( hFile != INVALID_HANDLE_VALUE ) { OVERLAPPED Ov; ZeroMemory( &Ov,sizeof(Ov)); if ( !(Ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL)) ) { dwLastError = GetLastError(); goto Cleanup; } if( !ReadFile( hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &Ov ) && GetLastError() != ERROR_IO_PENDING ) { dwLastError = GetLastError(); goto Cleanup; } if( WaitForSingleObject(Ov.hEvent, gdwServerInstallTimeOut) == WAIT_TIMEOUT ) { CancelIo(hFile); WaitForSingleObject(Ov.hEvent, INFINITE); } if( !GetOverlappedResult(hFile, &Ov, lpNumberOfBytesRead, FALSE) ) dwLastError = GetLastError(); Cleanup: if ( Ov.hEvent ) CloseHandle(Ov.hEvent); } else { dwLastError = ERROR_INVALID_HANDLE; SetLastError( dwLastError ); } return dwLastError; } BOOL WriteOverlapped( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead ) { DWORD dwLastError = ERROR_SUCCESS; if( hFile != INVALID_HANDLE_VALUE ) { OVERLAPPED Ov; ZeroMemory( &Ov,sizeof(Ov)); if ( !(Ov.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL)) ) { dwLastError = GetLastError(); goto Cleanup; } if( !WriteFile( hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &Ov ) && GetLastError() != ERROR_IO_PENDING ) { dwLastError = GetLastError(); goto Cleanup; } if( WaitForSingleObject(Ov.hEvent, gdwServerInstallTimeOut) == WAIT_TIMEOUT ) { CancelIo(hFile); WaitForSingleObject(Ov.hEvent, INFINITE); } if( !GetOverlappedResult(hFile, &Ov, lpNumberOfBytesRead, FALSE) ) dwLastError = GetLastError(); Cleanup: if ( Ov.hEvent ) CloseHandle(Ov.hEvent); } else { dwLastError = ERROR_INVALID_HANDLE; SetLastError(dwLastError); } return (dwLastError == ERROR_SUCCESS); } ULONG_PTR AlignToRegType( IN ULONG_PTR Data, IN DWORD RegType ) { // // Alings the value if Data to the boundary // dictated by the type of data read from registry. // ULONG_PTR Boundary; switch ( RegType ) { // // Binary data could store any kind of data. The pointer is casted // to LPDWORD or LPBOOL so make sure it is native aligned. // case REG_BINARY: { Boundary = sizeof(ULONG_PTR); } break; case REG_SZ: case REG_EXPAND_SZ: case REG_MULTI_SZ: { Boundary = sizeof(WCHAR); } break; case REG_DWORD: case REG_DWORD_BIG_ENDIAN: { Boundary = sizeof(DWORD32); } break; case REG_QWORD: { Boundary = sizeof(DWORD64); } break; case REG_NONE: default: { Boundary = sizeof(ULONG_PTR); } } return (Data + (Boundary - 1))&~(Boundary - 1); } /*++ Routine Name BuildAclStruct Routine Description: Helper function. Builds a vector of ACEs to allow all access to administrators and system. The caller has to free the pstrName fields Arguments: cElements - number of elements in the array pExplAcc - vector of aces Return Value: WIN32 error code --*/ DWORD BuildAclStruct( IN DWORD cElements, IN OUT EXPLICIT_ACCESS *pExplAcc ) { DWORD dwError = ERROR_INVALID_PARAMETER; if (pExplAcc && cElements==2) { PSID pAdminSid = NULL; PSID pSystemSid = NULL; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; // // Get SID for the built in system account // dwError = AllocateAndInitializeSid(&NtAuthority, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pSystemSid) && AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSid) ? ERROR_SUCCESS : GetLastError(); if (dwError == ERROR_SUCCESS) { // // Initialize the EXPLICIT_ACCESS with information about administrators // pExplAcc[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; pExplAcc[0].Trustee.pMultipleTrustee = NULL; pExplAcc[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; pExplAcc[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; pExplAcc[0].Trustee.ptstrName = (PTSTR)pAdminSid; pExplAcc[0].grfAccessMode = GRANT_ACCESS; pExplAcc[0].grfAccessPermissions = GENERIC_ALL; pExplAcc[0].grfInheritance = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; // // Initialize the EXPLICIT_ACCESS with information about the system // pExplAcc[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; pExplAcc[1].Trustee.pMultipleTrustee = NULL; pExplAcc[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; pExplAcc[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; pExplAcc[1].Trustee.ptstrName = (PTSTR)pSystemSid; pExplAcc[1].grfAccessMode = GRANT_ACCESS; pExplAcc[1].grfAccessPermissions = GENERIC_ALL; pExplAcc[1].grfInheritance = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE; } else { // // Note that we never end up here and have pAdminSid not NULL. However, for the // sake of consisentcy and extensibility we attempt to clean up both strcutures // if (pSystemSid) FreeSid(pSystemSid); if (pAdminSid) FreeSid(pAdminSid); } } return dwError; } /*++ Routine Name CreateProtectedDirectory Routine Description: Creates a directory with full access only to admins and to the system. Contained objects inherit these permissions. Arguments: pszDir - directory name Return Value: WIN32 error code --*/ DWORD CreateProtectedDirectory( IN LPCWSTR pszDir ) { DWORD dwError = ERROR_INVALID_PARAMETER; if (pszDir) { SECURITY_DESCRIPTOR SecDesc; SECURITY_ATTRIBUTES SecAttr; PACL pDacl = NULL; EXPLICIT_ACCESS ExplicitAccessVector[2] = {0}; if ((dwError = InitializeSecurityDescriptor(&SecDesc, SECURITY_DESCRIPTOR_REVISION) ? ERROR_SUCCESS : GetLastError()) == ERROR_SUCCESS && // // Initialize the ExplicitAccessVector // (dwError = BuildAclStruct(COUNTOF(ExplicitAccessVector), ExplicitAccessVector)) == ERROR_SUCCESS && // // Initialize the DACL // (dwError = SetEntriesInAcl(COUNTOF(ExplicitAccessVector), ExplicitAccessVector, NULL, &pDacl)) == ERROR_SUCCESS && // // Set the DACL in the security descriptor // (dwError = SetSecurityDescriptorDacl(&SecDesc, TRUE, pDacl, FALSE) ? ERROR_SUCCESS : GetLastError()) == ERROR_SUCCESS && // // Check if the security descriptor is valid. Function does not set last error // (dwError = IsValidSecurityDescriptor(&SecDesc) ? ERROR_SUCCESS : ERROR_INVALID_SECURITY_DESCR) == ERROR_SUCCESS) { // // Put the security descriptor in the security attribute // SecAttr.bInheritHandle = FALSE; SecAttr.nLength = sizeof(SecAttr); SecAttr.lpSecurityDescriptor = &SecDesc; dwError = CreateDirectory(pszDir, &SecAttr) ? ERROR_SUCCESS : GetLastError(); } // // The ptstrName here points to a sid obtained via AllocAndInitializeSid // if (ExplicitAccessVector[0].Trustee.ptstrName) { FreeSid(ExplicitAccessVector[0].Trustee.ptstrName); } // // The ptstrName here points to a sid obtained via AllocAndInitializeSid // if (ExplicitAccessVector[1].Trustee.ptstrName) { FreeSid((PSID)ExplicitAccessVector[1].Trustee.ptstrName); } } DBGMSG(DBG_CLUSTER, ("CreateProtectedDirectory returns Win32 error %u\n", dwError)); return dwError; } /*++ Routine Name CopyFileToDirectory Routine Description: Copies a file to a directory. File is fully qualified. The function takes a pszDestDir and up to 3 directories. It will create the dir: pszRoot\pszDir1\pszDir2\pszDir3 and copy the file over there. This is a helper function for installing drivers on clusters. The directory strcuture is created with special privileges. Only the system and administrators have access to it. Arguments: pssDestDirt - destination directory pszDir1 - optional pszDir2 - optional pszdir3 - optional pszFullFileName - qualified file path Return Value: WIN32 error code --*/ DWORD CopyFileToDirectory( IN LPCWSTR pszFullFileName, IN LPCWSTR pszDestDir, IN LPCWSTR pszDir1, IN LPCWSTR pszDir2, IN LPCWSTR pszDir3 ) { DWORD dwError = ERROR_INVALID_PARAMETER; LPWSTR pszFile = NULL; // // Our pszfullFileName must contain at least one "\" // if (pszFullFileName && pszDestDir && (pszFile = wcsrchr(pszFullFileName, L'\\'))) { LPCWSTR ppszArray[] = {pszDir1, pszDir2, pszDir3}; WCHAR szNewPath[MAX_PATH]; DWORD uIndex; DBGMSG(DBG_CLUSTER, ("CopyFileToDirectory\n\tpszFullFile "TSTR"\n\tpszDest "TSTR"\n\tDir1 "TSTR"\n\tDir2 "TSTR"\n\tDir3 "TSTR"\n", pszFullFileName, pszDestDir, pszDir1, pszDir2, pszDir3)); // // Prepare buffer for the loop (initialize to pszDestDir) // Create destination root directory, if not existing // if ((dwError = StrNCatBuff(szNewPath, MAX_PATH, pszDestDir, NULL)) == ERROR_SUCCESS && (DirectoryExists((LPWSTR)pszDestDir) || (dwError = CreateProtectedDirectory(pszDestDir)) == ERROR_SUCCESS)) { for (uIndex = 0; uIndex < COUNTOF(ppszArray) && dwError == ERROR_SUCCESS; uIndex++) { // // Append the first directory to the path and // Create the directory if not existing // if (ppszArray[uIndex] && (dwError = StrNCatBuff(szNewPath, MAX_PATH, szNewPath, L"\\", ppszArray[uIndex], NULL)) == ERROR_SUCCESS && !DirectoryExists(szNewPath) && !CreateDirectoryWithoutImpersonatingUser(szNewPath)) { dwError = GetLastError(); } } // // Create the destination file full name and copy the file // if (dwError == ERROR_SUCCESS && (dwError = StrNCatBuff(szNewPath, MAX_PATH, szNewPath, pszFile, NULL)) == ERROR_SUCCESS) { dwError = CopyFile(pszFullFileName, szNewPath, FALSE) ? ERROR_SUCCESS : GetLastError(); } DBGMSG(DBG_CLUSTER, ("CopyFileToDirectory szNewPath "TSTR"\n", szNewPath)); } } DBGMSG(DBG_CLUSTER, ("CopyFileToDirectory returns Win32 error %u\n", dwError)); return dwError; } /*++ Routine Name PropagateMonitorToCluster Routine Description: For a cluster we keep the following printing resources in the cluster data base: drivers, printers, ports, procs. We can also have cluster aware port monitors. When the spooler initializes those objects, it reads the data from the cluster data base. When we write those objects we pass a handle to a key to some spooler functions. (Ex WriteDriverIni) The handle is either to the local registry or to the cluster database. This procedure is not safe with language monitors. LMs keep data in the registry so you need to supply a handle to a key in the reg. They don't work with clusters. This function will do this: 1) write an association key in the cluster data base. We will have information like (lm name, dll name) (see below where we store this) 2) copy the lm dll to the cluster disk. When we fail over we have all it take to install the lm on the local node if needed. Theoretically this function works for both language and port monitors. But it is useless when applied to port monitors. Arguments: pszName - monitor name pszDllName - dll name of the monitor pszEnvName - envionment string Ex "Windows NT x86" pszEnvDir - the path on disk for the environement Ex w32x86 pIniSpooler - cluster spooler Return Value: WIN32 error code --*/ DWORD PropagateMonitorToCluster( IN LPCWSTR pszName, IN LPCWSTR pszDLLName, IN LPCWSTR pszEnvName, IN LPCWSTR pszEnvDir, IN PINISPOOLER pIniSpooler ) { DWORD dwError = ERROR_INVALID_PARAMETER; HKEY hKeyEnvironments; HKEY hKeyCurrentEnv; HKEY hKeyMonitors; HKEY hKeyCurrentMon; SPLASSERT(pIniSpooler->SpoolerFlags & SPL_PRINT && pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER); if (pszName && pszDLLName && pszEnvName && pszEnvDir) { // // Check if we added an entry for this lang monitor already // The cluster database for the spooler resource looks like: // // Parameters // | // +- Environments // | | // | +- Windows NT x86 // | // +- OtherMonitors // | // +- Foo // | | // | +- Driver = Foo.dll // | // +- Bar // | // +- Driver = Bar.dll // if ((dwError = SplRegCreateKey(pIniSpooler->hckRoot, ipszClusterDatabaseEnvironments, 0, KEY_READ | KEY_WRITE, NULL, &hKeyEnvironments, NULL, pIniSpooler)) == ERROR_SUCCESS) { if ((dwError = SplRegCreateKey(hKeyEnvironments, pszEnvName, 0, KEY_READ | KEY_WRITE, NULL, &hKeyCurrentEnv, NULL, pIniSpooler)) == ERROR_SUCCESS) { if ((dwError = SplRegCreateKey(hKeyCurrentEnv, szClusterNonAwareMonitors, 0, KEY_READ | KEY_WRITE, NULL, &hKeyMonitors, NULL, pIniSpooler)) == ERROR_SUCCESS) { if ((dwError = SplRegCreateKey(hKeyMonitors, pszName, 0, KEY_READ | KEY_WRITE, NULL, &hKeyCurrentMon, NULL, pIniSpooler)) == ERROR_SUCCESS) { DWORD cbNeeded = 0; // // Check if this driver already exists in the database // if ((dwError=SplRegQueryValue(hKeyCurrentMon, L"Driver", NULL, NULL, &cbNeeded, pIniSpooler))==ERROR_MORE_DATA) { DBGMSG(DBG_CLUSTER, ("CopyMonitorToClusterDisks "TSTR" already exists in cluster DB\n", pszName)); } else { if (RegSetString(hKeyCurrentMon, (LPWSTR)L"Driver", (LPWSTR)pszDLLName, &dwError, pIniSpooler)) { // // Copy monitor file to cluster disk // WCHAR szMonitor[MAX_PATH]; WCHAR szDestDir[MAX_PATH]; if (GetSystemDirectory(szMonitor, COUNTOF(szMonitor))) { if ((dwError = StrNCatBuff(szMonitor, COUNTOF(szMonitor), szMonitor, L"\\", pszDLLName, NULL)) == ERROR_SUCCESS && (dwError = StrNCatBuff(szDestDir, COUNTOF(szDestDir), pIniSpooler->pszClusResDriveLetter, L"\\", szClusterDriverRoot, NULL)) == ERROR_SUCCESS) { dwError = CopyFileToDirectory(szMonitor, szDestDir, pszEnvDir, NULL, NULL); } } else { dwError = GetLastError(); } } // // If anything failed, delete the entry from the database // if (dwError != ERROR_SUCCESS) { dwError = SplRegDeleteKey(hKeyMonitors, pszName, pIniSpooler); DBGMSG(DBG_CLUSTER, ("CopyMonitorToClusterDisks Error %u cleaned up cluster DB\n", dwError)); } } SplRegCloseKey(hKeyCurrentMon, pIniSpooler); } SplRegCloseKey(hKeyMonitors, pIniSpooler); } SplRegCloseKey(hKeyCurrentEnv, pIniSpooler); } SplRegCloseKey(hKeyEnvironments, pIniSpooler); } } DBGMSG(DBG_CLUSTER, ("PropagateMonitorToCluster returns Win32 error %u\n", dwError)); return dwError; } /*++ Routine Name InstallMonitorFromCluster Routine Description: For a cluster we keep the following printing resources in the cluster data base: drivers, printers, ports, procs. We can also have cluster aware port monitors. When the spooler initializes those objects, it reads the data from the cluster data base. When we write those objects we pass a handle to a key to some spooler functions. (Ex WriteDriverIni) The handle is either to the local registry or to the cluster database. This procedure is not safe with language monitors. LMs keep data in the registry so you need to supply a handle to a key in the reg. They don't work with clusters. This function will do this: 1) read an association key in the cluster data base. We will have information like (lm name, dll name) (see below where we store this) 2) copy the lm dll from the cluster disk to the local disk. 3) install the monitor with the local spooler Theoretically this function works for both language and port monitors. But it is useless when applied to port monitors. Arguments: pszName - monitor name pszEnvName - envionment string Ex "Windows NT x86" pszEnvDir - the path on disk for the environement Ex w32x86 pIniSpooler - cluster spooler Return Value: WIN32 error code --*/ DWORD InstallMonitorFromCluster( IN LPCWSTR pszName, IN LPCWSTR pszEnvName, IN LPCWSTR pszEnvDir, IN PINISPOOLER pIniSpooler ) { DWORD dwError = ERROR_INVALID_PARAMETER; HKEY hKeyParent; HKEY hKeyMon; SPLASSERT(pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER); if (pszName && pszEnvName && pszEnvDir) { HKEY hKeyEnvironments; HKEY hKeyCurrentEnv; HKEY hKeyMonitors; HKEY hKeyCurrentMon; // // Check if we added an entry for this lang monitor already // The cluster database for the spooler resource looks like: // // Parameters // | // +- Environments // | | // | +- Windows NT x86 // | // +- OtherMonitors // | // +- Foo // | | // | +- Driver = Foo.dll // | // +- Bar // | // +- Driver = Bar.dll // if ((dwError = SplRegOpenKey(pIniSpooler->hckRoot, ipszClusterDatabaseEnvironments, KEY_READ, &hKeyEnvironments, pIniSpooler)) == ERROR_SUCCESS) { if ((dwError = SplRegOpenKey(hKeyEnvironments, pszEnvName, KEY_READ, &hKeyCurrentEnv, pIniSpooler)) == ERROR_SUCCESS) { if ((dwError = SplRegOpenKey(hKeyCurrentEnv, szClusterNonAwareMonitors, KEY_READ, &hKeyMonitors, pIniSpooler)) == ERROR_SUCCESS) { if ((dwError = SplRegOpenKey(hKeyMonitors, pszName, KEY_READ, &hKeyCurrentMon, pIniSpooler)) == ERROR_SUCCESS) { LPWSTR pszDLLName = NULL; DWORD cchDLLName = 0; if (RegGetString(hKeyCurrentMon, (LPWSTR)L"Driver", &pszDLLName, &cchDLLName, &dwError, TRUE, pIniSpooler)) { // // We found the monitor entry in the cluster DB // WCHAR szSource[MAX_PATH]; WCHAR szDest[MAX_PATH]; if (GetSystemDirectory(szDest, COUNTOF(szDest))) { if ((dwError = StrNCatBuff(szDest, COUNTOF(szDest), szDest, L"\\", pszDLLName, NULL)) == ERROR_SUCCESS && (dwError = StrNCatBuff(szSource, COUNTOF(szSource), pIniSpooler->pszClusResDriveLetter, L"\\", szClusterDriverRoot, L"\\", pszEnvDir, L"\\", pszDLLName, NULL)) == ERROR_SUCCESS) { // // Copy file from K:\PrinterDrivers\W32x86\foo.dll to // WINDIR\system32\foo.dll // if (CopyFile(szSource, szDest, FALSE)) { MONITOR_INFO_2 Monitor; Monitor.pDLLName = pszDLLName; Monitor.pName = (LPWSTR)pszName; Monitor.pEnvironment = (LPWSTR)pszEnvName; DBGMSG(DBG_CLUSTER, ("InstallMonitorFromCluster "TSTR" copied\n", pszDLLName)); // // Call AddMonitor to the local spooler // if (!SplAddMonitor(NULL, 2, (LPBYTE)&Monitor, pLocalIniSpooler)) { dwError = GetLastError(); } } else { dwError = GetLastError(); } } } else { dwError = GetLastError(); } } SplRegCloseKey(hKeyCurrentMon, pIniSpooler); } SplRegCloseKey(hKeyMonitors, pIniSpooler); } SplRegCloseKey(hKeyCurrentEnv, pIniSpooler); } SplRegCloseKey(hKeyEnvironments, pIniSpooler); } } DBGMSG(DBG_CLUSTER, ("InstallMonitorFromCluster returns Win32 error %u\n", dwError)); return dwError; } /*++ Routine Name CopyNewerOrOlderFiles Routine Description: Copies all newer or older files from the source directory to the dest dir. If you supply a bool function that takes 2 parameters, it will apply that function for each copied file. func(NULL, file.) This is useful when I have to caopy over ICM profiles. Then I can resuse this function and have it install those profiles as it copies them. Arguments: pszSourceDir - source directory string pszDestDir - destination directory string pfn - optional functin to be applied on each copied file Return Value: WIN32 error code --*/ DWORD CopyNewerOrOlderFiles( IN LPCWSTR pszSourceDir, IN LPCWSTR pszDestDir, IN BOOL (WINAPI *pfn)(LPWSTR, LPWSTR) ) { DWORD dwError = ERROR_INVALID_PARAMETER; if (pszSourceDir && pszDestDir) { WCHAR szSearchPath[MAX_PATH]; // // Build the search path. We look for all files // if ((dwError = StrNCatBuff(szSearchPath, COUNTOF(szSearchPath), pszSourceDir, L"\\*", NULL)) == ERROR_SUCCESS) { WIN32_FIND_DATA SourceFindData; HANDLE hSourceFind; // // Find first file that meets the criteria // if ((hSourceFind = FindFirstFile(szSearchPath, &SourceFindData)) != INVALID_HANDLE_VALUE) { do { WCHAR szMasterPath[MAX_PATH]; // // Search for the rest of the files. We are interested in files that are not directories // if (!(SourceFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (dwError = StrNCatBuff(szMasterPath, COUNTOF(szMasterPath), pszDestDir, L"\\", SourceFindData.cFileName, NULL)) == ERROR_SUCCESS) { WIN32_FIND_DATA MasterFindData; HANDLE hMasterFind; BOOL bCopyFile = TRUE; WCHAR szFile[MAX_PATH]; // // Check if the file found in source dir exists in the dest dir // if ((hMasterFind = FindFirstFile(szMasterPath, &MasterFindData)) != INVALID_HANDLE_VALUE) { // // Do not copy file if source and dest have same time stamp // if (!CompareFileTime(&SourceFindData.ftLastWriteTime, &MasterFindData.ftLastWriteTime)) { bCopyFile = FALSE; } FindClose(hMasterFind); } // // File either not found in dest dir, or it has a different timp stamp // if (bCopyFile && (dwError = StrNCatBuff(szFile, COUNTOF(szFile), pszSourceDir, L"\\", SourceFindData.cFileName, NULL)) == ERROR_SUCCESS && (dwError = CopyFile(szFile, szMasterPath, FALSE) ? ERROR_SUCCESS : GetLastError()) == ERROR_SUCCESS && pfn) { dwError = (*pfn)(NULL, szFile) ? ERROR_SUCCESS : GetLastError(); } } } while (dwError == ERROR_SUCCESS && FindNextFile(hSourceFind, &SourceFindData)); FindClose(hSourceFind); } else if ((dwError = GetLastError()) == ERROR_PATH_NOT_FOUND || dwError == ERROR_FILE_NOT_FOUND) { // // No directory or files, success // dwError = ERROR_SUCCESS; } } } DBGMSG(DBG_CLUSTER, ("CopyNewerOrOlderFiles returns Win32 error %u\n", dwError)); return dwError; } /*++ Routine Name CopyICMFromClusterDiskToLocalDisk Routine Description: Copies all newer or older files from the source directory to the destination directory. The source directory is the ICM directory on the cluster disk and the destination is the ICM directory on the local machine for the cluster spooler. This function will also install the icm profiles with the local machine/ Arguments: pIniSpooler - pointer to cluster spooler structure Return Value: WIN32 error code --*/ DWORD CopyICMFromClusterDiskToLocalDisk( IN PINISPOOLER pIniSpooler ) { DWORD dwError = ERROR_SUCCESS; WCHAR szSource[MAX_PATH]; WCHAR szDest[MAX_PATH]; SPLASSERT(pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER); if ((dwError = StrNCatBuff(szDest, COUNTOF(szDest), pIniSpooler->pDir, L"\\Drivers\\Color", NULL)) == ERROR_SUCCESS && (dwError = StrNCatBuff(szSource, COUNTOF(szSource), pIniSpooler->pszClusResDriveLetter, L"\\", szClusterDriverRoot, L"\\", L"Color", NULL)) == ERROR_SUCCESS) { HMODULE hLib; typedef BOOL (WINAPI *PFN)(LPWSTR, LPWSTR); PFN pfn; // // Make sure the directory on the local disk exists. We will copy files // from K:\Printerdrivers\Color to // WINDIR\system32\spool\drivers\clus-spl-guid\drivers\color // CreateCompleteDirectory(szDest); if ((hLib = LoadLibrary(L"mscms.dll")) && (pfn = (PFN)GetProcAddress(hLib, "InstallColorProfileW"))) { dwError = CopyNewerOrOlderFiles(szSource, szDest, pfn); } else { dwError = GetLastError(); } if (hLib) { FreeLibrary(hLib); } DBGMSG(DBG_CLUSTER, ("CopyICMFromClusterDiskToLocalDisk "TSTR" "TSTR" Error %u\n", szSource, szDest, dwError)); } return dwError; } /*++ Routine Name CopyICMFromLocalDiskToClusterDisk Routine Description: Copies all newer or older files from the source directory to the destination directory. The source directory is the ICM directory on the local machine for the cluster spooler. The destination is the ICM directory on the cluster disk Arguments: pIniSpooler - pointer to cluster spooler structure Return Value: WIN32 error code --*/ DWORD CopyICMFromLocalDiskToClusterDisk( IN PINISPOOLER pIniSpooler ) { DWORD dwError = ERROR_SUCCESS; WCHAR szSource[MAX_PATH]; WCHAR szDest[MAX_PATH]; SPLASSERT(pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER); if ((dwError = StrNCatBuff(szSource, COUNTOF(szSource), pIniSpooler->pDir, L"\\Drivers\\Color", NULL)) == ERROR_SUCCESS && (dwError = StrNCatBuff(szDest, COUNTOF(szDest), pIniSpooler->pszClusResDriveLetter, L"\\", szClusterDriverRoot, L"\\", L"Color", NULL)) == ERROR_SUCCESS && // // Make sure the destination on the cluster disk exists. We need to create // it with special access rights. (only admins and system can read/write) // ((dwError = CreateProtectedDirectory(szDest)) == ERROR_SUCCESS || dwError == ERROR_ALREADY_EXISTS)) { dwError = CopyNewerOrOlderFiles(szSource, szDest, NULL); DBGMSG(DBG_CLUSTER, ("CopyICMFromLocalDiskToClusterDisk "TSTR" "TSTR" Error %u\n", szSource, szDest, dwError)); } return dwError; } /*++ Routine Name CreateClusterSpoolerEnvironmentsStructure Routine Description: A pIniSpooler needs a list of all possible pIniEnvironemnts. For the local spooler the setup creates the necessary environments keys in the registry under HKLM\System\CCS\Control\Print\Environments. We need to propagate the same structure for each cluster spooler in the cluster data base. Relies on the fact that pLocalIniSpooler is initialized fisrt (among all pinispoolers) This function builds the following strucutre in the cluster database Parameters | +- Environments | | | +- Windows NT x86 | | (Directory = w32x86) | | | +- Windows 4.0 | | (Directory = win40) | | Arguments: pIniSpooler - pointer to cluster spooler structure Return Value: WIN32 error code --*/ DWORD CreateClusterSpoolerEnvironmentsStructure( IN PINISPOOLER pIniSpooler ) { HKEY hEnvironmentsKey; DWORD dwError; SPLASSERT(pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER); if ((dwError = SplRegCreateKey(pIniSpooler->hckRoot, pIniSpooler->pszRegistryEnvironments, 0, KEY_ALL_ACCESS, NULL, &hEnvironmentsKey, NULL, pIniSpooler)) == ERROR_SUCCESS) { PINIENVIRONMENT pIniEnvironment; // // Loop through the environments of the local spooler // for (pIniEnvironment = pLocalIniSpooler->pIniEnvironment; pIniEnvironment && dwError == ERROR_SUCCESS; pIniEnvironment = pIniEnvironment->pNext) { HKEY hKeyCurrentEnv; if ((dwError = SplRegCreateKey(hEnvironmentsKey, pIniEnvironment->pName, 0, KEY_ALL_ACCESS, NULL, &hKeyCurrentEnv, NULL, pIniSpooler)) == ERROR_SUCCESS) { HKEY hKeyPrtProc; // // Set the value Directory (ex. = W32X86) and create // the print processor key // if (RegSetString(hKeyCurrentEnv, szDirectory, pIniEnvironment->pDirectory, &dwError, pIniSpooler) && (dwError = SplRegCreateKey(hKeyCurrentEnv, szPrintProcKey, 0, KEY_ALL_ACCESS, NULL, &hKeyPrtProc, NULL, pIniSpooler)) == ERROR_SUCCESS) { SplRegCloseKey(hKeyPrtProc, pIniSpooler); } SplRegCloseKey(hKeyCurrentEnv, pIniSpooler); } } SplRegCloseKey(hEnvironmentsKey, pIniSpooler); } DBGMSG(DBG_CLUSTER, ("CreateClusterSpoolerEnvironmentsStructure returns dwError %u\n\n", dwError)); return dwError; } /*++ Routine Name AddLocalDriverToClusterSpooler Routine Description: Adds a driver that exists on the local inispooler to the cluster spooler. It will also add all versions of that driver. We use this function in the upgrade scenario. For a certain printer, we need to propage the driver (and all the versions of that driver) to the cluster data base and cluster disk. Arguments: pIniSpooler - pointer to cluster spooler structure Return Value: WIN32 error code --*/ DWORD AddLocalDriverToClusterSpooler( IN LPCWSTR pszDriver, IN PINISPOOLER pIniSpooler ) { DWORD dwError = ERROR_SUCCESS; PINIENVIRONMENT pIniEnv; PINIVERSION pIniVer; SplInSem(); // // Traverse all environments and versions // for (pIniEnv=pLocalIniSpooler->pIniEnvironment; pIniEnv; pIniEnv=pIniEnv->pNext) { for (pIniVer=pIniEnv->pIniVersion; pIniVer; pIniVer=pIniVer->pNext) { PINIDRIVER pIniDriver = (PINIDRIVER)FindIniKey((PINIENTRY)pIniVer->pIniDriver, (LPWSTR)pszDriver); if (pIniDriver) { DRIVER_INFO_6 DriverInfo = {0}; WCHAR szDriverFile[MAX_PATH]; WCHAR szDataFile[MAX_PATH]; WCHAR szConfigFile[MAX_PATH]; WCHAR szHelpFile[MAX_PATH]; WCHAR szPrefix[MAX_PATH]; LPWSTR pszzDependentFiles = NULL; // // Get fully qualified driver file paths. We will call add printer // driver without using the scratch directory // if ((dwError = StrNCatBuff(szDriverFile, COUNTOF(szDriverFile), pLocalIniSpooler->pDir, szDriversDirectory, L"\\", pIniEnv->pDirectory, L"\\", pIniVer->szDirectory, L"\\", pIniDriver->pDriverFile, NULL)) == ERROR_SUCCESS && (dwError = StrNCatBuff(szDataFile, COUNTOF(szDataFile), pLocalIniSpooler->pDir, szDriversDirectory, L"\\", pIniEnv->pDirectory, L"\\", pIniVer->szDirectory, L"\\", pIniDriver->pDataFile, NULL)) == ERROR_SUCCESS && (dwError = StrNCatBuff(szConfigFile, COUNTOF(szConfigFile), pLocalIniSpooler->pDir, szDriversDirectory, L"\\", pIniEnv->pDirectory, L"\\", pIniVer->szDirectory, L"\\", pIniDriver->pConfigFile, NULL)) == ERROR_SUCCESS && (dwError = StrNCatBuff(szHelpFile, COUNTOF(szHelpFile), pLocalIniSpooler->pDir, szDriversDirectory, L"\\", pIniEnv->pDirectory, L"\\", pIniVer->szDirectory, L"\\", pIniDriver->pHelpFile, NULL)) == ERROR_SUCCESS && (dwError = StrNCatBuff(szPrefix, COUNTOF(szPrefix), pLocalIniSpooler->pDir, szDriversDirectory, L"\\", pIniEnv->pDirectory, L"\\", pIniVer->szDirectory, L"\\", NULL)) == ERROR_SUCCESS && (dwError = StrCatPrefixMsz(szPrefix, pIniDriver->pDependentFiles, &pszzDependentFiles)) == ERROR_SUCCESS) { DBGMSG(DBG_CLUSTER, ("AddLocalDrv szDriverFile "TSTR"\n", szDriverFile)); DBGMSG(DBG_CLUSTER, ("AddLocalDrv szDataFile "TSTR"\n", szDataFile)); DBGMSG(DBG_CLUSTER, ("AddLocalDrv szConfigFile "TSTR"\n", szConfigFile)); DBGMSG(DBG_CLUSTER, ("AddLocalDrv szHelpFile "TSTR"\n", szHelpFile)); DBGMSG(DBG_CLUSTER, ("AddLocalDrv szPrefix "TSTR"\n", szPrefix)); DriverInfo.pDriverPath = szDriverFile; DriverInfo.pName = pIniDriver->pName; DriverInfo.pEnvironment = pIniEnv->pName; DriverInfo.pDataFile = szDataFile; DriverInfo.pConfigFile = szConfigFile; DriverInfo.cVersion = pIniDriver->cVersion; DriverInfo.pHelpFile = szHelpFile; DriverInfo.pMonitorName = pIniDriver->pMonitorName; DriverInfo.pDefaultDataType = pIniDriver->pDefaultDataType; DriverInfo.pDependentFiles = pszzDependentFiles; DriverInfo.pszzPreviousNames = pIniDriver->pszzPreviousNames; DriverInfo.pszMfgName = pIniDriver->pszMfgName; DriverInfo.pszOEMUrl = pIniDriver->pszOEMUrl; DriverInfo.pszHardwareID = pIniDriver->pszHardwareID; DriverInfo.pszProvider = pIniDriver->pszProvider; DriverInfo.dwlDriverVersion = pIniDriver->dwlDriverVersion; DriverInfo.ftDriverDate = pIniDriver->ftDriverDate; LeaveSplSem(); if (!SplAddPrinterDriverEx(NULL, 6, (LPBYTE)&DriverInfo, APD_COPY_NEW_FILES, pIniSpooler, FALSE, DO_NOT_IMPERSONATE_USER)) { dwError = GetLastError(); } EnterSplSem(); } FreeSplMem(pszzDependentFiles); DBGMSG(DBG_CLUSTER, ("AddLocalDrv Env "TSTR" Ver "TSTR" Name "TSTR" Error %u\n", pIniEnv->pName, pIniVer->pName, pszDriver, dwError)); } } } return dwError; } /*++ Routine Name StrCatPerfixMsz Routine Description: Take a prefix which is a string Ex "C:\windows\" and a multi sz Ex: "a0b00" It will create: c:\windows\a0c:\windows\b00 The prefix must have a trailing "\". Arguments: pszPrefix - string to prefix all the strings in the msz pszzFiles - msz of files ppszzFullPathFiles - out param Return Value: WIN32 error code --*/ DWORD StrCatPrefixMsz( IN LPCWSTR pszPrefix, IN LPWSTR pszzFiles, OUT LPWSTR *ppszzFullPathFiles ) { DWORD dwError = ERROR_INVALID_PARAMETER; if (pszPrefix && ppszzFullPathFiles) { *ppszzFullPathFiles = NULL; if (pszzFiles) { WCHAR szNewPath[MAX_PATH] = {0}; LPWSTR psz; LPWSTR pszReturn; LPWSTR pszTemp; DWORD cbNeeded = 0; DWORD dwPrifxLen = wcslen(pszPrefix); // // We count the number of character of the string // that we need to allocate // for (psz = pszzFiles; psz && *psz;) { DWORD dwLen = wcslen(psz); cbNeeded += dwPrifxLen + dwLen + 1; psz += dwLen + 1; } // // Final \0 of the multi sz // cbNeeded++; // // Convert to number of bytes // cbNeeded *= sizeof(WCHAR); if (pszReturn = AllocSplMem(cbNeeded)) { for (psz = pszzFiles, pszTemp = pszReturn; psz && *psz; ) { wcscpy(pszTemp, pszPrefix); wcscat(pszTemp, psz); pszTemp += wcslen(pszTemp); *pszTemp = L'\0'; pszTemp++; psz += wcslen(psz) + 1; } pszTemp = L'\0'; // // Set out param // *ppszzFullPathFiles = pszReturn; dwError = ERROR_SUCCESS; } else { dwError = GetLastError(); } } else { // // NULL input multi sz, nothing to do // dwError = ERROR_SUCCESS; } } DBGMSG(DBG_CLUSTER, ("StrCatPerfixMsz returns %u\n", dwError)); return dwError; } /*++ Routine Name ClusterSplReadUpgradeKey Routine Description: After the first reboot following an upgrade of a node, the cluster service informs the resdll that a version change occured. At this time out spooler resource may be running on another node or may not be actie at all. Thus the resdll writes a value in the local registry. The vaue name is the GUID for the spooler resource, the value is DWORD 1. When the cluster spooler resource fails over on this machine it (i.e. now) it queries for that value to know if it needs to preform post upgrade operations, like upgrading the printer drivers. Arguments: pszResource - string respresenation of GUID for the cluster resource pdwVlaue - will contain the value in the registry for the GUID Return Value: WIN32 error code --*/ DWORD ClusterSplReadUpgradeKey( IN LPCWSTR pszResourceID, OUT LPDWORD pdwValue ) { DWORD dwError = ERROR_INVALID_PARAMETER; HKEY hkRoot = NULL; HKEY hkUpgrade = NULL; if (pszResourceID && pdwValue) { *pdwValue = 0; if ((dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SPLREG_CLUSTER_LOCAL_ROOT_KEY, 0, KEY_READ, &hkRoot)) == ERROR_SUCCESS && (dwError = RegOpenKeyEx(hkRoot, SPLREG_CLUSTER_UPGRADE_KEY, 0, KEY_READ, &hkUpgrade)) == ERROR_SUCCESS) { DWORD cbNeeded = sizeof(DWORD); dwError = RegQueryValueEx(hkUpgrade, pszResourceID, NULL, NULL, (LPBYTE)pdwValue, &cbNeeded); } if (hkUpgrade) RegCloseKey(hkUpgrade); if (hkRoot) RegCloseKey(hkRoot); // // Regardless of what happened, return success // dwError = ERROR_SUCCESS; } return dwError; } /*++ Routine Name ClusterSplReadUpgradeKey Routine Description: After the first reboot following an upgrade of a node, the cluster service informs the resdll that a version change occured. At this time out spooler resource may be running on another node or may not be actie at all. Thus the resdll writes a value in the local registry. The vaue name is the GUID for the spooler resource, the value is DWORD 1. When the cluster spooler resource fails over on this machine it (i.e. now) it queries for that value to know if it needs to preform post upgrade operations, like upgrading the printer drivers. After the spooler preforms upgrade taks, it will delete the value corresponding to its GUID. Also if that value is the only one under the SPLREG_CLUSTER_UPGRADE_KEY key, it will delete that key. Arguments: pszResource - string respresenation of GUID for the cluster resource Return Value: WIN32 error code --*/ DWORD ClusterSplDeleteUpgradeKey( IN LPCWSTR pszResourceID ) { DWORD dwError = ERROR_INVALID_PARAMETER; HKEY hkRoot = NULL; HKEY hkUpgrade = NULL; if ((dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SPLREG_CLUSTER_LOCAL_ROOT_KEY, 0, KEY_READ, &hkRoot)) == ERROR_SUCCESS && (dwError = RegOpenKeyEx(hkRoot, SPLREG_CLUSTER_UPGRADE_KEY, 0, KEY_ALL_ACCESS, &hkUpgrade)) == ERROR_SUCCESS) { DWORD cValues = 0; dwError = RegDeleteValue(hkUpgrade, pszResourceID); if (RegQueryInfoKey(hkUpgrade, NULL, NULL, NULL, NULL, NULL, NULL, &cValues, NULL, NULL, NULL, NULL) == ERROR_SUCCESS && !cValues) { RegDeleteKey(hkRoot, SPLREG_CLUSTER_UPGRADE_KEY); } if (hkUpgrade) RegCloseKey(hkUpgrade); if (hkRoot) RegCloseKey(hkRoot); } return dwError; } /*++ Routine Name RunProcess Routine Description: Creates a process. Waits for it to terminate. Arguments: pszExe - program to execute (muist be fully qualfied) pszCommand - command line to execute dwTimeOut - time to wait for the process to terminate pszExitCode - pointer to reveice exit code of process Return Value: WIN32 error code --*/ DWORD RunProcess( IN LPCWSTR pszExe, IN LPCWSTR pszCommand, IN DWORD dwTimeOut, OUT LPDWORD pdwExitCode OPTIONAL ) { DWORD dwError = ERROR_INVALID_PARAMETER; if (pszCommand && pszExe) { PROCESS_INFORMATION ProcInfo = {0}; STARTUPINFO StartInfo = {0}; StartInfo.cb = sizeof(StartInfo); StartInfo.dwFlags = 0; if (!CreateProcess(pszExe, (LPWSTR)pszCommand, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &StartInfo, &ProcInfo)) { dwError = GetLastError(); } else { if (WaitForSingleObject(ProcInfo.hProcess, dwTimeOut) == WAIT_OBJECT_0) { // // Process executed fine // dwError = ERROR_SUCCESS; } if (pdwExitCode && !GetExitCodeProcess(ProcInfo.hProcess, pdwExitCode)) { *pdwExitCode = 0; } CloseHandle(ProcInfo.hThread); CloseHandle(ProcInfo.hProcess); } } return dwError; } /*++ Routine Name GetLocalArchEnv Routine Description: Helper function. Returns a pointer to the environment that matches the architecture of the local machine. The environemnt is taken off the pInSpooler passed as argument Arguments: pIniSpooler - pointer to spooler structure Return Value: PINIENVIRONMENT --*/ PINIENVIRONMENT GetLocalArchEnv( IN PINISPOOLER pIniSpooler ) { SplInSem(); // // The local spooler and cluster spooler do not share the same Environment structures. // return pIniSpooler && pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER ? FindEnvironment(szEnvironment, pIniSpooler) : pThisEnvironment; } /*++ Routine Name ClusterFindLanguageMonitor Routine Description: If a valid monitor name is specified and the monitor is not found in the specified pIniSpooler, then the function will try to install the monitor from the cluster disk. Arguments: pszMonitor - monitor name pszEnvName - name of environment of the lm pIniSpooler - pointer to cluster spooler structure Return Value: Win32 error code --*/ DWORD ClusterFindLanguageMonitor( IN LPCWSTR pszMonitor, IN LPCWSTR pszEnvName, IN PINISPOOLER pIniSpooler ) { DWORD dwError = ERROR_SUCCESS; // // This is the moment where we check if we need to add the monitor // if (pszMonitor && *pszMonitor) { PINIMONITOR pIniLangMonitor; EnterSplSem(); // // We need to find the language monitor in pLocalIniSpooler // LMs are not cluster aware, so the cluster pIniSpooler doesn't know about them // pIniLangMonitor = FindMonitor(pszMonitor, pLocalIniSpooler); LeaveSplSem(); if (!pIniLangMonitor) { PINIENVIRONMENT pIniEnvironment; EnterSplSem(); pIniEnvironment = FindEnvironment(pszEnvName, pIniSpooler); LeaveSplSem(); if (pIniEnvironment) { DBGMSG(DBG_CLUSTER, ("ClusterFindLanguageMonitor Trying to install LangMonitor "TSTR"\n", pszMonitor)); // // We try to install the monitor from the cluster disk to the local spooler // dwError = InstallMonitorFromCluster(pszMonitor, pIniEnvironment->pName, pIniEnvironment->pDirectory, pIniSpooler); } else { dwError = ERROR_INVALID_ENVIRONMENT; } } } DBGMSG(DBG_CLUSTER, ("ClusterFindLanguageMonitor LangMonitor "TSTR" return Win32 error %u\n", pszMonitor, dwError)); return dwError; } /*++ Routine Name WriteTimeStamp Routine Description: Opens the key hkRoot\pszSubkey1\...\pszSubKey5 and writes the value of szClusDrvTimeStamp (binary data representing a system time) Arguments: hkRoot - handle to driver key SysTime - system time structure pszSubKey1 - subkey of the root key pszSubKey2 - subkey of key1, can be null pszSubKey3 - subkey of key2, can be null pszSubKey4 - subkey of key3, can be null pszSubKey5 - subkey of key4, can be null pIniSpooler - spooler, can be NULL Return Value: WIN32 error code --*/ DWORD WriteTimeStamp( IN HKEY hkRoot, IN SYSTEMTIME SysTime, IN LPCWSTR pszSubKey1, IN LPCWSTR pszSubKey2, IN LPCWSTR pszSubKey3, IN LPCWSTR pszSubKey4, IN LPCWSTR pszSubKey5, IN PINISPOOLER pIniSpooler ) { DWORD dwError = ERROR_INVALID_PARAMETER; if (hkRoot) { LPCWSTR ppszKeyNames[] = {NULL, pszSubKey1, pszSubKey2, pszSubKey3, pszSubKey4, pszSubKey5}; HKEY pKeyHandles[] = {hkRoot, NULL, NULL, NULL, NULL, NULL}; DWORD uIndex; dwError = ERROR_SUCCESS; // // Open all the keys // for (uIndex = 1; uIndex < COUNTOF(ppszKeyNames) && dwError == ERROR_SUCCESS && ppszKeyNames[uIndex]; uIndex++) { DBGMSG(DBG_CLUSTER, ("KEY "TSTR"\n", ppszKeyNames[uIndex])); dwError = SplRegCreateKey(pKeyHandles[uIndex-1], ppszKeyNames[uIndex], 0, KEY_ALL_ACCESS, NULL, &pKeyHandles[uIndex], NULL, pIniSpooler); } // // If we opened successfully the keys that we wanted, write the value // if (dwError == ERROR_SUCCESS && !RegSetBinaryData(pKeyHandles[uIndex-1], szClusDrvTimeStamp, (LPBYTE)&SysTime, sizeof(SysTime), &dwError, pIniSpooler)) { dwError = GetLastError(); } // // Close any keys that we opened // for (uIndex = 1; uIndex < COUNTOF(ppszKeyNames); uIndex++) { if (pKeyHandles[uIndex]) { SplRegCloseKey(pKeyHandles[uIndex], pIniSpooler); } } } DBGMSG(DBG_CLUSTER, ("WriteTimeStamp returns Win32 error %u\n", dwError)); return dwError; } /*++ Routine Name ReadTimeStamp Routine Description: Opens the key hkRoot\pszSubkey1\...\pszSubKey5 and reads the value of szClusDrvTimeStamp (binary data representing a system time) Arguments: hkRoot - handle to driver key pSysTime - pointer to allocated system time structure pszSubKey1 - subkey of the root key pszSubKey2 - subkey of key1, can be null pszSubKey3 - subkey of key2, can be null pszSubKey4 - subkey of key3, can be null pszSubKey5 - subkey of key4, can be null pIniSpooler - spooler, can be NULL Return Value: WIN32 error code --*/ DWORD ReadTimeStamp( IN HKEY hkRoot, IN OUT SYSTEMTIME *pSysTime, IN LPCWSTR pszSubKey1, IN LPCWSTR pszSubKey2, IN LPCWSTR pszSubKey3, IN LPCWSTR pszSubKey4, IN LPCWSTR pszSubKey5, IN PINISPOOLER pIniSpooler ) { DWORD dwError = ERROR_INVALID_PARAMETER; if (hkRoot && pSysTime) { LPCWSTR ppszKeyNames[] = {NULL, pszSubKey1, pszSubKey2, pszSubKey3, pszSubKey4, pszSubKey5}; HKEY pKeyHandles[] = {hkRoot, NULL, NULL, NULL, NULL, NULL}; DWORD uIndex; dwError = ERROR_SUCCESS; // // Open all the keys // for (uIndex = 1; uIndex < COUNTOF(ppszKeyNames) && dwError == ERROR_SUCCESS && ppszKeyNames[uIndex]; uIndex++) { dwError = SplRegCreateKey(pKeyHandles[uIndex-1], ppszKeyNames[uIndex], 0, KEY_ALL_ACCESS, NULL, &pKeyHandles[uIndex], NULL, pIniSpooler); } // // If we opened successfully the keys that we wanted, write the value // if (dwError == ERROR_SUCCESS) { DWORD cbSize = sizeof(SYSTEMTIME); dwError = SplRegQueryValue(pKeyHandles[uIndex-1], szClusDrvTimeStamp, NULL, (LPBYTE)pSysTime, &cbSize, pIniSpooler); } // // Close any keys that we opened // for (uIndex = 1; uIndex < COUNTOF(ppszKeyNames); uIndex++) { if (pKeyHandles[uIndex]) { SplRegCloseKey(pKeyHandles[uIndex], pIniSpooler); } } } DBGMSG(DBG_CLUSTER, ("ReadTimeStamp returns Win32 error %u\n", dwError)); return dwError; } /*++ Routine Name ClusterCheckDriverChanged Routine Description: Helper function for spooler start up. When we have a cluster spooler and we build the environments and drivers, we need to check if the drivers on the local machine (in print$\GUID) are in sync with the drivers on the cluster disk. We store a time stamp in the cluster data base. The time stamp indicate when the last update on the driver occured. The same type of time stamp is stroed in the lcoal registry (for our cluster spooler). If the 2 time stamp are different, then we need to call an add printer driver and use data from the cluster disk. The drivers on the cluster spooler were updated while it was running on a different node. Arguments: hClusterVersionKey - handle to the driver version key pszDriver - driver name pszEnv - driver environment name pszVer - driver version name pIniSpooler - spooler Return Value: TRUE - if the driver on the cluster disk is updated and we need to call add printer driver. If anything fails in this function, then we also return TRUE, to force our caller to update/add the driver in question. FALSE - if the drivers on the local mahcine and the cluster spooler are in sync --*/ BOOL ClusterCheckDriverChanged( IN HKEY hClusterVersionKey, IN LPCWSTR pszDriver, IN LPCWSTR pszEnv, IN LPCWSTR pszVer, IN PINISPOOLER pIniSpooler ) { BOOL bReturn = TRUE; if (hClusterVersionKey && pszDriver && pszEnv && pszVer) { SYSTEMTIME ClusterTime; SYSTEMTIME NodeTime; if (ReadTimeStamp(hClusterVersionKey, &ClusterTime, pszDriver, NULL, NULL, NULL, NULL, pIniSpooler) == ERROR_SUCCESS && ReadTimeStamp(HKEY_LOCAL_MACHINE, &NodeTime, ipszRegistryClusRepository, pIniSpooler->pszClusResID, pszEnv, pszVer, pszDriver, NULL) == ERROR_SUCCESS && !memcmp(&ClusterTime, &NodeTime, sizeof(SYSTEMTIME))) { bReturn = FALSE; } } DBGMSG(DBG_CLUSTER, ("ClusterCheckDriverChanged returns bool %u\n", bReturn)); return bReturn; } /*++ Routine Name IsLocalFile Routine Description: Checks if a file is on the local machine. If the file path is "\\machinename\sharename\...\filename" then machinename is checked against pIniSpooler->pMachineName and pIniSpooler->ppszOtherNames. Arguments: pszFileName - file name pIniSpooler - INISPOOLER structure Return Value: TRUE if the file is placed locally. FALSE if the file is placed remotely. --*/ BOOL IsLocalFile ( IN LPCWSTR pszFileName, IN PINISPOOLER pIniSpooler ) { LPWSTR pEndOfMachineName, pMachineName; BOOL bRetValue = TRUE; if (pszFileName && *pszFileName == L'\\' && *(pszFileName+1) == L'\\') { // // If first 2 charactes in pszFileName are '\\', // then search for the next '\\'. If found, then set it to 0, // to isolate the machine name. // pMachineName = (LPWSTR)pszFileName; if (pEndOfMachineName = wcschr(pszFileName + 2, L'\\')) { *pEndOfMachineName = 0; } bRetValue = CheckMyName(pMachineName, pIniSpooler); // // Restore pszFileName. // if (pEndOfMachineName) { *pEndOfMachineName = L'\\'; } } return bRetValue; } /*++ Routine Name IsEXEFile Routine Description: Checks if a file is a executable. The check is made against file extension, which isn't quite accurate. Arguments: pszFileName - file name Return Value: TRUE if the file extension is either .EXE or .DLL --*/ BOOL IsEXEFile( IN LPCWSTR pszFileName ) { BOOL bRetValue = FALSE; DWORD dwLength; LPWSTR pszExtension; if (pszFileName && *pszFileName) { dwLength = wcslen(pszFileName); if (dwLength > COUNTOF(L".EXE") - 1) { pszExtension = (LPWSTR)pszFileName + dwLength - (COUNTOF(L".EXE") - 1); if (_wcsicmp(pszExtension , L".EXE") == 0 || _wcsicmp(pszExtension , L".DLL") == 0) { bRetValue = TRUE; } } } return bRetValue; } /*++ Routine Name PackStringToEOB Routine Description: Copies a string to the end of buffer. The buffer must be big enough so that it can hold the string. This function is called by Get/Enum APIs that build BLOB buffers to send them with RPC. Arguments: pszSource - string to by copied to the end of buffer pEnd - a pointer to the end of a pre-allocated buffer. Return Value: The pointer to the end of buffer after the sting was appended. NULL if an error occured. --*/ LPBYTE PackStringToEOB( IN LPWSTR pszSource, IN LPBYTE pEnd ) { DWORD cbStr; // // Align the end of buffer to WORD boundaries. // WORD_ALIGN_DOWN(pEnd); if (pszSource && pEnd) { cbStr = (wcslen(pszSource) + 1) * sizeof(WCHAR); pEnd -= cbStr; CopyMemory(pEnd, pszSource, cbStr); } else { pEnd = NULL; } return pEnd; } LPVOID MakePTR ( IN LPVOID pBuf, IN DWORD Quantity ) /*++ Routine Name MakePTR Routine Description: Makes a pointer by adding a quantity to the beginning of a buffer. Arguments: pBuf - pointer to buffer DWORD - quantity Return Value: LPVOID pointer --*/ { return (LPVOID)((ULONG_PTR)pBuf + (ULONG_PTR)Quantity); } DWORD MakeOffset ( IN LPVOID pFirst, IN LPVOID pSecond ) /*++ Routine Name MakeOffset Routine Description: Substarcts two pointers. Arguments: pFirst - pointer to buffer pSecond - pointer to buffer Return Value: DWORD --*/ { return (DWORD)((ULONG_PTR)pFirst - (ULONG_PTR)pSecond); } /*++ Routine Name IsValidPrinterName Routine Description: Checks if a string is a valid printer name. Arguments: pszPrinter - pointer to string cchMax - max number of chars to scan Return Value: TRUE - the string is a valid printer name FALSE - the string is a invalid printer name. The function set the last error to ERROR_INVALID_PRINTER_NAME in this case --*/ BOOL IsValidPrinterName( IN LPCWSTR pszPrinter, IN DWORD cchMax ) { DWORD Error = ERROR_INVALID_PRINTER_NAME; // // A printer name is of the form: // // \\s\p or p // // The name cannot contain the , character. Note that the add printer // wizard doesn't accept "!" as a valid printer name. We wanted to do // the same here, but we regressed in app compat with 9x apps. // The number of \ in the name is 0 or 3 // If the name contains \, then the fist 2 chars must be \. // The printer name cannot end in \. // After leading "\\" then next char must not be \ // The minimum length is 1 character // The maximum length is MAX_UNC_PRINTER_NAME // if (!IsBadStringPtr(pszPrinter, cchMax) && pszPrinter && *pszPrinter) { UINT uSlashCount = 0; UINT uLen = 0; LPCWSTR p; Error = ERROR_SUCCESS; // // Count characters // for (p = pszPrinter; *p && uLen <= cchMax; p++, uLen++) { if (*p == L',') { Error = ERROR_INVALID_PRINTER_NAME; break; } else if (*p == L'\\') { uSlashCount++; } } // // Perform validation // if (Error == ERROR_SUCCESS && // // Validate length // (uLen > cchMax || // // The printer name has either no \, or exactly 3 \. // uSlashCount && uSlashCount != 3 || // // A printer name that contains 3 \, must have the first 2 chars \ and the 3 not \. // The last char cannot be \. // Ex "\Foo", "F\oo", "\\\Foo", "\\Foo\" are invalid. // Ex. "\\srv\bar" is valid. // uSlashCount == 3 && (pszPrinter[0] != L'\\' || pszPrinter[1] != L'\\' || pszPrinter[2] == L'\\' || pszPrinter[uLen-1] == L'\\'))) { Error = ERROR_INVALID_PRINTER_NAME; } } SetLastError(Error); return Error == ERROR_SUCCESS; } /*++ Routine Name SplPowerEvent Routine Description: Checks if the spooler is ready for power management events like hibernation/stand by. If we have printing jobs that are not in an error state or offline, then we deny the powering down request. Arguments: Event - power management event Return Value: TRUE - the spooler allowd the system to be powered down FALSE - the spooler denies the request for powering down --*/ BOOL SplPowerEvent( DWORD Event ) { BOOL bAllow = TRUE; EnterSplSem(); switch (Event) { case PBT_APMQUERYSUSPEND: { PINISPOOLER pIniSpooler; for (pIniSpooler = pLocalIniSpooler; pIniSpooler && bAllow; pIniSpooler = pIniSpooler->pIniNextSpooler) { PINIPRINTER pIniPrinter; for (pIniPrinter = pIniSpooler->pIniPrinter; pIniPrinter && bAllow; pIniPrinter = pIniPrinter->pNext) { PINIJOB pIniJob; for (pIniJob = pIniPrinter->pIniFirstJob; pIniJob && bAllow; pIniJob = pIniJob->pIniNextJob) { if (pIniJob->Status & JOB_PRINTING && !(pIniJob->Status & JOB_ERROR | pIniJob->Status & JOB_OFFLINE)) { bAllow = FALSE; } } } } // // If we allow system power down, then we need to stop scheduling jobs // if (bAllow) { ResetEvent(PowerManagementSignal); } break; } case PBT_APMQUERYSUSPENDFAILED: case PBT_APMRESUMESUSPEND: case PBT_APMRESUMEAUTOMATIC: // // Set the event to allow the spooler to continue scheudling jobs // SetEvent(PowerManagementSignal); break; default: // // We ignore any other power management event // break; } LeaveSplSem(); return bAllow; } /*++ Routine Name IsCallViaRPC Routine Description: Checks if the caller of this function came in the spooler server via RPC or not. Arguments: None Return Value: TRUE - the caller came in via RPC FALSE - the caller did not come via RPC --*/ BOOL IsCallViaRPC( IN VOID ) { UINT uType; return I_RpcBindingInqTransportType(NULL, &uType) == RPC_S_NO_CALL_ACTIVE ? FALSE : TRUE; } /*++ Routine Name MergeMultiSz Routine Description: This merges two multisz strings such that there is a resulting multisz strings that has no duplicate strings internal. This algorithm is currently N^2 which could be improved. It is currently being called from the driver code and the dependent files are not a large set. Arguments: pszMultiSz1 - The first multi-sz string. cchMultiSz1 - The length of the multi-sz string. pszMultiSz2 - The second multi-sz string. cchMultiSz2 - The length of the second multi-sz string. ppszMultiSzMerge - The merged multi-sz string. pcchMultiSzMerge - The number of characters in the merge, this could be less than the allocated buffer size. Return Value: FALSE on failure, LastError is set. --*/ BOOL MergeMultiSz( IN PCWSTR pszMultiSz1, IN DWORD cchMultiSz1, IN PCWSTR pszMultiSz2, IN DWORD cchMultiSz2, OUT PWSTR *ppszMultiSzMerge, OUT DWORD *pcchMultiSzMerge OPTIONAL ) { BOOL bRet = FALSE; PWSTR pszNewMultiSz = NULL; DWORD cchNewMultiSz = 0; *ppszMultiSzMerge = NULL; if (pcchMultiSzMerge) { *pcchMultiSzMerge = 0; } if (cchMultiSz1 || cchMultiSz2) { // // Code assumes that these are at least 1 in the allocation size. // cchMultiSz1 = cchMultiSz1 == 0 ? 1 : cchMultiSz1; cchMultiSz2 = cchMultiSz2 == 0 ? 1 : cchMultiSz2; // // The merged strings will be at most the size of both of them (if there are // no duplicates). // pszNewMultiSz = AllocSplMem((cchMultiSz1 + cchMultiSz2 - 1) * sizeof(WCHAR)); bRet = pszNewMultiSz != NULL; if (bRet) { // // Ensure that the multi-sz string is at least empty. // *pszNewMultiSz = L'\0'; } if (bRet && pszMultiSz1) { AddMultiSzNoDuplicates(pszMultiSz1, pszNewMultiSz); } if (bRet && pszMultiSz2) { AddMultiSzNoDuplicates(pszMultiSz2, pszNewMultiSz); } if (bRet) { cchNewMultiSz = GetMultiSZLen(pszNewMultiSz); } } if (bRet) { *ppszMultiSzMerge = pszNewMultiSz; if (pcchMultiSzMerge) { *pcchMultiSzMerge = cchNewMultiSz; } pszNewMultiSz = NULL; } FreeSplMem(pszNewMultiSz); return bRet; } /*++ Routine Name AddMultiSzNoDuplicates Routine Description: This adds all of the strings in a multisz string to a buffer (the buffer must be guaranteed to be large enough to accept the strings), it makes sure that there are no case insensitive duplicates in the list. Arguments: pszMultiSzIn - The multi-sz whose elements are being added. pszNewMultiSz - The buffer in which we are filling up the multi-sz Return Value: None. --*/ VOID AddMultiSzNoDuplicates( IN PCWSTR pszMultiSzIn, IN OUT PWSTR pszNewMultiSz ) { PCWSTR pszIn = NULL; for(pszIn = pszMultiSzIn; *pszIn; pszIn += wcslen(pszIn) + 1) { BOOL bStringFound = FALSE; PWSTR pszMerge = NULL; // // For each input string, run the merged multi-sz string and add it if // it is not already there. // for(pszMerge = pszNewMultiSz; *pszMerge; pszMerge += wcslen(pszMerge) + 1) { if (!_wcsicmp(pszIn, pszMerge)) { bStringFound = TRUE; break; } } // // If the string was not found in the multisz string, then add it to the end. // if (!bStringFound) { wcscpy(pszMerge, pszIn); pszMerge += wcslen(pszIn) + 1; // // Add the extra null terminator for now. // *pszMerge = '\0'; } } } /*++ Routine Name GetMultiSZLen Routine Description: This returns the number of characters in a multisz string, including NULLs. Arguments: pMultiSzSrc - The multisz string to search. Return Value: The number of characters in the string. --*/ DWORD GetMultiSZLen( IN LPWSTR pMultiSzSrc ) { DWORD dwLen = 0; LPWSTR pTmp = pMultiSzSrc; while( TRUE ) { dwLen += wcslen(pTmp) + 1; // Incude the terminating NULL char pTmp = pMultiSzSrc + dwLen; // Point to the beginning of the next string in the MULTI_SZ if( !*pTmp ) return ++dwLen; // Reached the end of the MULTI_SZ string. Add 1 to the count for the last NULL char. } }