/*++ Copyright (c) 2000-2002 Microsoft Corporation Module Name: EmulatePrinter.cpp Abstract: This is a general purpose shim to fix all problems we have seen that are remotely connected with printers. The shim fixes the following: 1) Apps call EnumPrinters passing only PRINTER_ENUM_LOCAL but expect to see network printers as well. For some reason Win9x enumerates network printers as well when this API is called with only PRINTER_ENUM_LOCAL set. 2) Apps call EnumPrinters passing only PRINTER_ENUM_DEFAULT. This works properly in win98, however the option does not exist in w2k. This API performs the equivalent. 3) EnumPrinters Level 5 is not really supported on NT. This API calls Level 2 and munges the data into a level 5 structure. 4) Win9x ignores pDefault parameter for OpenPrinter. Some native Win9x apps are unaware about this and assume it is safe to use PRINTER_ALL_ACCESS value for DesiredAccess flag, member of pDefault parameter, to open either local printer or remote printer server. But Windows NT requires SERVER_ALL_ACCESS set for this flag to access remote printer server. To emulate Win9x behavior, we override pDefault with NULL value. 5) If an app calls one of several print APIs with a NULL printer name, looks up and supplies the default printer name, or derives it from other params. 6) Verifies a correct handle was passed to SetPrinter. Win98 does this at the start and if its a bad handle never uses the passed Information, however w2k does not check the handle till after looking at the information. This can cause an error if Level is 2 and the print buffer is null due to a missing check in SetPrinterA. (note: this was fixed in whistler). 7) Verifies that the stack is correct after the proc set in SetAbortProc is called. 8) Verifies that an initialized DEVMODEA has been passed to ResetDCA. 9) Checks GetProfileStringA for a WINDOWS DEVICE (i.e. printer). If one is requested then make sure the string is not being truncated, if it is then save the full printer name for later use. 10) Checks for a -1 in the nFromPage for PrintDlgA and corrects it to a zero. Note: the OS should handle this as per the manual, however print team no-fixed it as too risky to change since -1 is a special value in their code. Notes: This is a general purpose shim. This code from this shim was originally in two seperate shims enumnetworkprinters and handlenullprintername. Also added another SHIM EmulateStartPage to this. History: 11/08/00 mnikkel created 12/07/00 prashkud Added StartPage to this. 01/25/01 mnikkel Removed W routines, they were causing problems and were not needed. 02/07/01 mnikkel Added check for too long a string, removed fixed printer name sizes. 02/27/2001 robkenny Converted to use tcs.h 05/21/2001 mnikkel Added PrintDlgA check 09/13/2001 mnikkel Changed so that level 5 data being created from Level 2 data is only done on win2k. Level 5 data was fixed for XP. Also added check so shim works with printers shared out on win9X while running on XP. 12/15/2001 mnikkel Corrected bug in shim where default printer flag was not being set in enumprintersa. 02/20/2002 mnikkel Major cleanup to remove buffer overrun possibilities. Added check for No printer in GetProfileString routine --*/ #include "precomp.h" #include IMPLEMENT_SHIM_BEGIN(EmulatePrinter) #include "ShimHookMacro.h" #define MAX_PRINTER_NAME 221 #define MAX_DRIVERPORT_NAME 50 APIHOOK_ENUM_BEGIN APIHOOK_ENUM_ENTRY(DocumentPropertiesA) APIHOOK_ENUM_ENTRY(OpenPrinterA) APIHOOK_ENUM_ENTRY(SetPrinterA) APIHOOK_ENUM_ENTRY(CreateDCA) APIHOOK_ENUM_ENTRY(ResetDCA) APIHOOK_ENUM_ENTRY(EnumPrintersA) APIHOOK_ENUM_ENTRY(GetProfileStringA) APIHOOK_ENUM_ENTRY(SetAbortProc) APIHOOK_ENUM_ENTRY(StartPage) APIHOOK_ENUM_ENTRY(DeviceCapabilitiesA) APIHOOK_ENUM_ENTRY(AddPrinterConnectionA) APIHOOK_ENUM_ENTRY(DeletePrinterConnectionA) APIHOOK_ENUM_ENTRY(PrintDlgA) APIHOOK_ENUM_END typedef int (WINAPI *_pfn_SetAbortProc)(HDC hdc, ABORTPROC lpAbortProc); CString g_csFullPrinterName(""); CString g_csPartialPrinterName(""); CRITICAL_SECTION g_critSec; BOOL g_bWin2k = FALSE; /*++ These functions munge data from a Level 2 Information structure into a Level 5 information structure. --*/ BOOL MungeInfo2TOInfo5_A( PRINTER_INFO_2A* pInfo2, DWORD cbBuf, DWORD dwInfo2Returned, PRINTER_INFO_5A* pInfo5, LPDWORD pcbNeeded, LPDWORD pcbReturned) { DWORD dwStringBufferSize = 0; LPSTR lpStringBuffer = NULL; // Sanity check, should not occur. if (pInfo2 == NULL || pInfo5 == NULL) { return FALSE; } // First calculate buffer size needed for (DWORD i = 0; i < dwInfo2Returned; i++) { if (pInfo2[i].pPrinterName) { dwStringBufferSize += strlen(pInfo2[i].pPrinterName); } dwStringBufferSize++; if (pInfo2[i].Attributes & PRINTER_ATTRIBUTE_NETWORK && !(pInfo2[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) && pInfo2[i].pServerName != NULL && pInfo2[i].pShareName != NULL) { dwStringBufferSize += strlen(pInfo2[i].pServerName) + 1; dwStringBufferSize += strlen(pInfo2[i].pShareName) + 1; } else { if (pInfo2[i].pPortName) { dwStringBufferSize += strlen(pInfo2[i].pPortName); } dwStringBufferSize++; } } // set the buffer size needed *pcbNeeded = dwInfo2Returned * sizeof(PRINTER_INFO_5A) + dwStringBufferSize; // verify that buffer passed in is big enough. if (cbBuf < *pcbNeeded) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; } // Allocate the Level 5 information structure lpStringBuffer = ((LPSTR) pInfo5) + dwInfo2Returned * sizeof(PRINTER_INFO_5A); // Munge the Level 2 information into the Level 5 structure for (i = 0; i < dwInfo2Returned; i++) { // Copy the printername from level 2 to level 5 structure. if (pInfo2[i].pPrinterName) { if (StringCchCopyA( lpStringBuffer, cbBuf, pInfo2[i].pPrinterName) != S_OK) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; } } else { if (StringCchCopyA( lpStringBuffer, cbBuf, "") != S_OK) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; } } pInfo5[i].pPrinterName = lpStringBuffer; lpStringBuffer += strlen(pInfo2[i].pPrinterName) + 1; // Copy in attributes to level 5 structure and set defaults pInfo5[i].Attributes = pInfo2[i].Attributes; pInfo5[i].DeviceNotSelectedTimeout = 15000; // Use defaults here pInfo5[i].TransmissionRetryTimeout = 45000; // Use defaults here // Check for a network printer if (pInfo2[i].Attributes & PRINTER_ATTRIBUTE_NETWORK && !(pInfo2[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) && pInfo2[i].pServerName != NULL && pInfo2[i].pShareName != NULL) { // For network printer create the win98 style port name if (StringCchCopyA( lpStringBuffer, cbBuf, pInfo2[i].pServerName) != S_OK || StringCchCatA( lpStringBuffer, cbBuf, "\\" ) != S_OK || StringCchCatA( lpStringBuffer, cbBuf, pInfo2[i].pShareName) != S_OK) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; } } else { // Not network printer, just copy in port name if (pInfo2[i].pPortName) { if (StringCchCopyA( lpStringBuffer, cbBuf, pInfo2[i].pPortName) != S_OK) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; } } else { if (StringCchCopyA( lpStringBuffer, cbBuf, "") != S_OK) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; } } } pInfo5[i].pPortName = lpStringBuffer; lpStringBuffer += strlen(pInfo2[i].pPortName) + 1; } // Set the number of structures munged *pcbReturned = dwInfo2Returned; return TRUE; } /*++ Our Callback routine for SetAbortProc, this routine verifies that the stack is correct. --*/ DWORD g_dwGuardNum = 0xABCD8765; DWORD g_dwFailed = 0; BOOL CALLBACK AbortProcHook( ABORTPROC pfnOld, // address of old ABORTPROC HDC hdc, // handle to DC int iError // error value ) { DWORD dwRet= 0; // Flag to track whether the stack was corrected. g_dwFailed = 0; // Push a Guard number on the stack, call their // abort procedure, then pop the stack till we // find our guard number __asm { push ebx push ecx push g_dwGuardNum push iError push hdc call pfnOld ; make call to their abort proc mov ecx,16 loc1: dec ecx pop ebx cmp ebx, g_dwGuardNum jne loc1 cmp ecx, 15 jz loc2 mov g_dwFailed, 1 loc2: pop ecx pop ebx mov dwRet, eax } if (g_dwFailed) { LOGN( eDbgLevelError, "[AbortProcHook] Fixing incorrect calling convention for AbortProc"); } return (BOOL) dwRet; } /*++ This stub function looks up the device name if pDeviceName is NULL --*/ LONG APIHOOK(DocumentPropertiesA)( HWND hWnd, HANDLE hPrinter, LPSTR pDeviceName, PDEVMODEA pDevModeOutput, PDEVMODEA pDevModeInput, DWORD fMode ) { LONG lRet = -1; PRINTER_INFO_2A *pPrinterInfo2A = NULL; // if they didn't supply a device name, we need to supply it. if (!pDeviceName) { LOGN( eDbgLevelError, "[DocumentPropertiesW] App passed NULL for pDeviceName."); if (hPrinter) { DWORD dwSizeNeeded = 0; DWORD dwSizeUsed = 0; // get the size GetPrinterA(hPrinter, 2, NULL, 0, &dwSizeNeeded); if (dwSizeNeeded != 0) { // allocate memory for the info pPrinterInfo2A = (PRINTER_INFO_2A*) malloc(dwSizeNeeded); if (pPrinterInfo2A) { // get the info if (GetPrinterA(hPrinter, 2, (LPBYTE)pPrinterInfo2A, dwSizeNeeded, &dwSizeUsed)) { pDeviceName = pPrinterInfo2A->pPrinterName; } } } } } if (!pDeviceName) { DPFN( eDbgLevelError, "[DocumentPropertiesA] Unable to gather correct pDeviceName." "Problem not fixed.\n"); } lRet = ORIGINAL_API(DocumentPropertiesA)( hWnd, hPrinter, pDeviceName, pDevModeOutput, pDevModeInput, fMode ); if (pPrinterInfo2A) { free(pPrinterInfo2A); } return lRet; } /*++ These functions handle the case of EnumPrinters being called with the PRINTER_ENUM_DEFAULT flag. --*/ BOOL EnumDefaultPrinterA( PRINTER_INFO_2A* pInfo2, LPBYTE pPrinterEnum, DWORD cbBuf, DWORD Level, PRINTER_INFO_5A* pInfo5, LPDWORD pcbNeeded, LPDWORD pcbReturned ) { LPSTR pszName = NULL; DWORD dwSize = 0; HANDLE hPrinter = NULL; BOOL bRet= FALSE; DWORD dwInfo2Needed = 0; DWORD dwDummy; BOOL bDefaultFail = TRUE; *pcbNeeded = 0; *pcbReturned = 0; // get the default printer name if (GetDefaultPrinterA(NULL, &dwSize) < 1) { // Now that we have the right size, allocate a buffer if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { pszName = (LPSTR) malloc(dwSize); if (pszName) { // Now get the default printer with the right buffer size. if (GetDefaultPrinterA(pszName, &dwSize) > 0) { if (OpenPrinterA(pszName, &hPrinter, NULL)) { bDefaultFail = FALSE; } } free(pszName); } } } if (bDefaultFail) { SetLastError(ERROR_INVALID_NAME); return FALSE; } // Printer Level 5 is not really supported on win2k. // We'll call Level 2 and munge the data into a level 5 structure. if ( g_bWin2k && Level == 5 && pcbNeeded != NULL && pcbReturned != NULL) { LOGN(eDbgLevelError, "[EnumPrintersA] EnumPrintersA called with Level 5 set." " Fixing up Level 5 information."); // get the size needed for the info2 data if (GetPrinterA(hPrinter, 2, NULL, 0, &dwInfo2Needed) == 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { pInfo2 = (PRINTER_INFO_2A *) malloc(dwInfo2Needed); // get the info2 data and munge into level 5 structure if (pInfo2 && GetPrinterA(hPrinter, 2, (LPBYTE)pInfo2, dwInfo2Needed, &dwDummy)) { bRet= MungeInfo2TOInfo5_A(pInfo2, cbBuf, 1, pInfo5, pcbNeeded, pcbReturned); } if (pInfo2) { free(pInfo2); } } } // Not win2k or not Level 5 so just get info else { *pcbReturned = 1; bRet = GetPrinterA(hPrinter, Level, pPrinterEnum, cbBuf, pcbNeeded); } // Close the printer ClosePrinter(hPrinter); return bRet; } /*++ These stub functions check for PRINTER_ENUM_DEFAULT, PRINTER_ENUM_LOCAL and Level 5 information structures. --*/ BOOL APIHOOK(EnumPrintersA)( DWORD Flags, LPSTR Name, DWORD Level, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcbReturned ) { BOOL bRet = FALSE; DWORD dwInfo2Needed = 0; DWORD dwInfo2Returned = 0; DWORD dwDummy; PRINTER_INFO_2A* pInfo2 = NULL; PRINTER_INFO_5A* pInfo5 = (PRINTER_INFO_5A *) pPrinterEnum; // Win2k doesn't handle DEFAULT case like win98 did, so we get // to do it for them. if (Flags == PRINTER_ENUM_DEFAULT ) { LOGN(eDbgLevelError, "[EnumPrintersA] Called with PRINTER_ENUM_DEFAULT flag." " Providing Default printer."); bRet = EnumDefaultPrinterA( pInfo2, pPrinterEnum, cbBuf, Level, pInfo5, pcbNeeded, pcbReturned); return bRet; } // For LOCAL also add in CONNECTIONS if (Flags == PRINTER_ENUM_LOCAL) { LOGN( eDbgLevelInfo, "[EnumPrintersA] Called only for " "PRINTER_ENUM_LOCAL. Adding PRINTER_ENUM_CONNECTIONS\n"); Flags = (PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL); } // Printer Level 5 is not really supported on win2k. // We'll call Level 2 and munge the data into a level 5 structure. if (g_bWin2k && Level == 5 && pcbNeeded != NULL && pcbReturned != NULL) { // get the size needed for the info2 data ORIGINAL_API(EnumPrintersA)(Flags, Name, 2, NULL, 0, &dwInfo2Needed, &dwInfo2Returned); if (dwInfo2Needed > 0) { // Printers found, get the info2 data and convert it to info5 pInfo2 = (PRINTER_INFO_2A *) malloc(dwInfo2Needed); if (pInfo2 && ORIGINAL_API(EnumPrintersA)(Flags, Name, 2, (LPBYTE) pInfo2, dwInfo2Needed, &dwDummy, &dwInfo2Returned) ) { bRet = MungeInfo2TOInfo5_A( pInfo2, cbBuf, dwInfo2Returned, pInfo5, pcbNeeded, pcbReturned); } if(pInfo2) { free(pInfo2); } } } else { bRet = ORIGINAL_API(EnumPrintersA)(Flags, Name, Level, pPrinterEnum, cbBuf, pcbNeeded, pcbReturned); } // For level 2 and level 5 there are some win95 only attributes // that need to be emulated. if ( (Level == 2 || Level == 5) && bRet && pPrinterEnum != NULL ) { DWORD dwSize; GetDefaultPrinterA(NULL, &dwSize); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { // Now that we have the right size, allocate a buffer LPSTR pszName = (LPSTR) malloc(dwSize); if (pszName) { // Now get the default printer with the right buffer size. if (GetDefaultPrinterA( pszName, &dwSize ) > 0) { if (Level == 2) { if (strcmp( pszName, ((PRINTER_INFO_2A*)pPrinterEnum)->pPrinterName) == 0) { ((PRINTER_INFO_2A*)pPrinterEnum)->Attributes |= PRINTER_ATTRIBUTE_DEFAULT; } } else { if (strcmp( pszName, ((PRINTER_INFO_5A*)pPrinterEnum)->pPrinterName) == 0) ((PRINTER_INFO_5A*)pPrinterEnum)->Attributes |= PRINTER_ATTRIBUTE_DEFAULT; } } free(pszName); } } } return bRet; } /*++ These stub functions substitute the default printer if the pPrinterName is NULL, also they set pDefault to NULL to emulate win9x behavior --*/ BOOL APIHOOK(OpenPrinterA)( LPSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSA pDefault ) { LPSTR pszName = NULL; DWORD dwSize; BOOL bDefaultFail = TRUE; BOOL bRet; if (!pPrinterName) { LOGN(eDbgLevelError, "[OpenPrinterA] App passed NULL for pPrinterName, using default printer."); // get the default printer name if (GetDefaultPrinterA(NULL, &dwSize) < 1) { // Now that we have the right size, allocate a buffer if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { pszName = (LPSTR) malloc(dwSize); if (pszName) { // Now get the default printer with the right buffer size. if (GetDefaultPrinterA( pszName, &dwSize ) > 0) { pPrinterName = pszName; bDefaultFail = FALSE; } } } } if (bDefaultFail) { DPFN(eDbgLevelError, "[OpenPrinterA] Unable to gather default pPrinterName.\n"); } } else { CSTRING_TRY { if (pPrinterName && !g_csPartialPrinterName.IsEmpty() && !g_csFullPrinterName.IsEmpty()) { CString csTemp(pPrinterName); if (0 == g_csPartialPrinterName.Compare(csTemp)) { pPrinterName = g_csFullPrinterName.GetAnsi(); } } } CSTRING_CATCH { // Do nothing } } if (pPrinterName) { DPFN( eDbgLevelInfo, "[OpenPrinterA] APIHOOK(OpenPrinterA: pPrinterName: %s\n", pPrinterName); } DPFN( eDbgLevelInfo, "[OpenPrinterA] APIHOOK(OpenPrinterA: pDefault: %x\n", pDefault); DPFN( eDbgLevelInfo, "[OpenPrinterA] APIHOOK(OpenPrinterA: overriding pDefault with NULL value\n"); bRet = ORIGINAL_API(OpenPrinterA)( pPrinterName, phPrinter, NULL); if (pszName) { free(pszName); } return bRet; } /*++ This stub function checks to see if the app is asking for the default printer string. If it is it will be returned as follows: PrinterName, Driver, Port On Win9x, if the printer is a network printer, Port is \\server\share and local printers are Port: (ex. LPT1:). On Win2k, if the printer is a network printer, Port is NeXX: and local printers are Port: . We must query EnumPrinters in order to emulate Win9x. Note: If the printer name is to large for the input buffer we trim it and keep track of the full name for later us in other printer APIs. --*/ DWORD APIHOOK(GetProfileStringA)( LPCSTR lpAppName, // section name LPCSTR lpKeyName, // key name LPCSTR lpDefault, // default string LPSTR lpReturnedString, // destination buffer DWORD nSize // size of destination buffer ) { LPSTR pszProfileString = NULL; if ( lpAppName && lpKeyName && 0 == _stricmp(lpAppName, "windows") && 0 == _stricmp(lpKeyName, "device" ) ) { DWORD dwSize = MAX_PRINTER_NAME + MAX_DRIVERPORT_NAME; DWORD dwProfileStringLen = 0; CSTRING_TRY { // loop until we have a large enough buffer do { if (pszProfileString != NULL) { free(pszProfileString); dwSize += MAX_PATH; } // Allocate the string pszProfileString = (LPSTR) malloc(dwSize); if (pszProfileString == NULL) { DPFN( eDbgLevelSpew, "[GetProfileStringA] Unable to allocate memory. Passing through."); //drop through if malloc fails goto DropThrough; } // Retrieve the profile string dwProfileStringLen = ORIGINAL_API(GetProfileStringA)( lpAppName, lpKeyName, lpDefault, pszProfileString, dwSize ); // exit out if the size gets over 8000 so we don't loop forever // if buffer is not large enough dwProfileStringLen will be dwSize - 1 since // neither lpAppName nor lpKeyName are NULL } while (dwProfileStringLen == dwSize-1 && dwSize < 8000); // Zero length profile string, drop through if (dwProfileStringLen == 0 || dwSize >= 8000) { DPFN( eDbgLevelSpew, "[GetProfileStringA] Bad profile string. Passing through."); goto DropThrough; } // separate out the printer, driver and port name. CStringToken csOrig(pszProfileString, L","); CString csPrinter; CString csDriver; CString csPort; csOrig.GetToken(csPrinter); csOrig.GetToken(csDriver); csOrig.GetToken(csPort); // If the printer, driver or the port are null, drop through. if (csPrinter.IsEmpty() || csDriver.IsEmpty() || csPort.IsEmpty()) { DPFN( eDbgLevelSpew, "[GetProfileStringA] Printer, Driver or Port were null. Passing through."); // Null printerdriver or printerport, drop through goto DropThrough; } DPFN( eDbgLevelError, "[GetProfileStringA] Printer <%S>\n Driver <%S>\n Port <%S>", csPrinter.Get(), csDriver.Get(), csPort.Get()); // Check to see if this is a network printer if (0 == csPort.ComparePart(L"Ne", 0, 2)) { PRINTER_INFO_2A* pInfo2 = NULL; DWORD dwInfo2Needed = 0; DWORD dwInfo2Returned = 0; DWORD dwDummy = 0; DWORD i = 0; BOOL bEnumPrintersSuccess = FALSE; BOOL bDefaultFound = FALSE; // Get the size of the Level 2 structure needed. bEnumPrintersSuccess = EnumPrintersA( PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL, NULL, 2, NULL, 0, &dwInfo2Needed, &dwInfo2Returned ); // Get the Level 2 Info structure for the printer. pInfo2 = (PRINTER_INFO_2A *) malloc(dwInfo2Needed); bEnumPrintersSuccess = EnumPrintersA( PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL, NULL, 2, (LPBYTE) pInfo2, dwInfo2Needed, &dwDummy, &dwInfo2Returned ); if (bEnumPrintersSuccess) { // Search for default printer in PRINTER_INFO_2 array for (i = 0; i < dwInfo2Returned; i++) { CString csTemp(pInfo2[i].pPrinterName); if (0 == csPrinter.Compare(csTemp)) { bDefaultFound = TRUE; break; } } // Default printer was found if (bDefaultFound) { // Double check that this is a network printer and does not have // local attribute if (pInfo2[i].Attributes & PRINTER_ATTRIBUTE_NETWORK && !(pInfo2[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) && pInfo2[i].pServerName != NULL && pInfo2[i].pShareName != NULL) { // Modify the Port to conform with Win9x standards. LOGN( eDbgLevelInfo, "[GetProfileStringA] Altering default printer string returned by GetProfileStringA.\n"); DPFN( eDbgLevelInfo, "[GetProfileStringA] Old: %s\n", pszProfileString); csPort = CString(pInfo2[i].pServerName) + L"\\" + CString(pInfo2[i].pShareName); } else { if (pInfo2[i].pPortName == NULL) { // Bad portname, pass through goto DropThrough; } // Just copy in the port csPort = CString(pInfo2[i].pPortName); } } } free(pInfo2); } // Create a profile string based off the modifed strings. CString csProfile(csPrinter); csProfile += L"," + csDriver + L"," + csPort; dwProfileStringLen = csProfile.GetLength()+1; // If the size they give is big enough, then return. if (dwProfileStringLen <= nSize) { StringCchCopyA( lpReturnedString, nSize, csProfile.GetAnsi()); DPFN( eDbgLevelInfo, "[GetProfileStringA] Default Printer: %s Size: %d\n", lpReturnedString, strlen(lpReturnedString)); return strlen(lpReturnedString); } // Modify the printer name and keep a global of the original if the printer // name causes the profile string output buffer to overflow. // If the size we need to reduce it by is greater than the size of // the printer name we're screwed, pass through. DWORD dwPrinterNameSize = csPrinter.GetLength() - (dwProfileStringLen - nSize); if (dwPrinterNameSize > 0) { DPFN( eDbgLevelInfo, "[GetProfileStringA] Reducing printer name by %d characters.\n", dwProfileStringLen - nSize ); LOGN( eDbgLevelInfo, "[GetProfileStringA] Reducing printer name by %d characters.\n", dwProfileStringLen - nSize ); EnterCriticalSection(&g_critSec); // save the partial and full printer names for later use. g_csPartialPrinterName = csPrinter.Left(dwPrinterNameSize); g_csFullPrinterName = csPrinter; // Create new profile string based off of partial printer name StringCchCopyA( lpReturnedString, nSize, g_csPartialPrinterName.GetAnsi()); StringCchCatA( lpReturnedString, nSize, ","); StringCchCatA( lpReturnedString, nSize, csDriver.GetAnsi()); StringCchCatA( lpReturnedString, nSize, ","); StringCchCatA( lpReturnedString, nSize, csPort.GetAnsi()); LeaveCriticalSection(&g_critSec); DPFN( eDbgLevelInfo, "[GetProfileStringA] Partial: %s\n Full: %s\n", g_csPartialPrinterName.GetAnsi(), g_csFullPrinterName.GetAnsi() ); DPFN( eDbgLevelInfo, "[GetProfileStringA] New: %s Size: %d\n", lpReturnedString, strlen(lpReturnedString)); // return the modified string size. return strlen(lpReturnedString); } } CSTRING_CATCH { // Do nothing, just drop through. } } DropThrough: // Either an error occurred or its not asking for default printer. // pass through. return ORIGINAL_API(GetProfileStringA)(lpAppName, lpKeyName, lpDefault, lpReturnedString, nSize); } /*++ This stub function pulls the device name from the DEVMODE if pszDevice is NULL and the DC is not for DISPLAY --*/ HDC APIHOOK(CreateDCA)( LPCSTR pszDriver, LPCSTR pszDevice, LPCSTR pszPort, CONST DEVMODEA *pdm ) { // if they've used a NULL device, but included a printer devmode, // fill in the device name from the printer devmode if (!pszDevice && pdm && (!pszDriver || _stricmp(pszDriver, "DISPLAY") != 0)) { LOGN( eDbgLevelError, "[CreateDCA] App passed NULL for pszDevice. Fixing."); pszDevice = (LPCSTR)pdm->dmDeviceName; } return ORIGINAL_API(CreateDCA)( pszDriver, pszDevice, pszPort, pdm ); } /*++ This stub function verifies that ResetDCA hasn't been handed an uninitialized InitData. --*/ HDC APIHOOK(ResetDCA)( HDC hdc, CONST DEVMODEA *lpInitData ) { // Sanity checks to make sure we aren't getting garbage // or bad values. if (lpInitData && (lpInitData->dmSize > sizeof( DEVMODEA ) || ( lpInitData->dmSpecVersion != 0x401 && lpInitData->dmSpecVersion != 0x400 && lpInitData->dmSpecVersion != 0x320 ) ) ) { LOGN( eDbgLevelError, "[ResetDCA] App passed bad DEVMODE structure, nulling."); return ORIGINAL_API(ResetDCA)( hdc, NULL ); } return ORIGINAL_API(ResetDCA)( hdc, lpInitData ); } /*++ These stub functions verify that SetPrinter has a valid handle before proceeding. --*/ BOOL APIHOOK(SetPrinterA)( HANDLE hPrinter, // handle to printer object DWORD Level, // information level LPBYTE pPrinter, // printer data buffer DWORD Command // printer-state command ) { BOOL bRet; if (hPrinter == NULL) { LOGN(eDbgLevelError, "[SetPrinterA] Called with null handle."); if (pPrinter == NULL) LOGN( eDbgLevelError, "[SetPrinterA] Called with null printer data buffer."); return FALSE; } else if (pPrinter == NULL) { LOGN(eDbgLevelError, "[SetPrinterA] Called with null printer data buffer."); return FALSE; } bRet= ORIGINAL_API(SetPrinterA)( hPrinter, Level, pPrinter, Command); DPFN( eDbgLevelSpew, "[SetPrinterA] Level= %d Command= %d Ret= %d\n", Level, Command, bRet ); return bRet; } /*++ This routine hooks the SetAbortProc and replaces their callback with ours. --*/ int APIHOOK(SetAbortProc)( HDC hdc, // handle to DC ABORTPROC lpAbortProc // abort function ) { lpAbortProc = (ABORTPROC) HookCallback(lpAbortProc, AbortProcHook); return ORIGINAL_API(SetAbortProc)( hdc, lpAbortProc ); } /*++ When apps start printing, they set a viewport on the printDC. They then call StartPage which has a different behaviour on 9x and WinNT. On 9x, a next call to StartPage resets the DC attributes to the default values.However on NT, the next call to StartPage does not reset the DC attributes. So, on 9x all subsequent output setup and drawing calls are carried out with a (0,0) viewport but on NT the viewport is leftover from its initial call. Since some apps(eg. Quicken 2000 and 2001) expect the API setting the (0,0) viewport,the result will be that the text and the lines are clipped on the left and top of the page. Here we hook StartPage and call SetViewportOrgEx(hdc, 0, 0, NULL) to set the viewport to (0,0) on every call to StartPage to emulate the 9x behaviour. --*/ BOOL APIHOOK(StartPage)( HDC hdc ) { if (SetViewportOrgEx(hdc, 0, 0, NULL)) { // We have now made the device point(viewport) map to (0, 0). LOGN(eDbgLevelInfo, "[StartPage] Setting the device point map to (0,0)."); } else { LOGN(eDbgLevelError, "[StartPage] Unable to set device point map to (0,0)." "Failed in a call to SetViewportOrgEx"); } return ORIGINAL_API(StartPage)(hdc); } /*++ This stub function verifies that DeviceCapabilities is using a correct printer name. --*/ DWORD APIHOOK(DeviceCapabilitiesA)( LPCSTR pDevice, LPCSTR pPort, WORD fwCapability, LPSTR pOutput, CONST DEVMODE *pDevMode ) { DWORD dwRet; CSTRING_TRY { if ( pDevice && !g_csPartialPrinterName.IsEmpty() && !g_csFullPrinterName.IsEmpty()) { CString csTemp(pDevice); if (0 == g_csPartialPrinterName.Compare(csTemp)) { pDevice = g_csFullPrinterName.GetAnsi(); } } } CSTRING_CATCH { // Do nothing } dwRet= ORIGINAL_API(DeviceCapabilitiesA)( pDevice, pPort, fwCapability, pOutput, pDevMode ); if (pDevice && pPort) { DPFN( eDbgLevelSpew, "[DeviceCapabilitiesA] pDevice= %s pPort= %s fwC= %d Out= %x RC= %d\n", pDevice, pPort, fwCapability, pOutput, dwRet ); } else { DPFN( eDbgLevelSpew, "[DeviceCapabilitiesA] fwC= %d Out= %x RC= %d\n", fwCapability, pOutput, dwRet ); } return dwRet; } /*++ This stub function verifies that AddPrinterConnection is using a correct printer name. --*/ BOOL APIHOOK(AddPrinterConnectionA)( LPSTR pName ) { CSTRING_TRY { if (pName && !g_csPartialPrinterName.IsEmpty() && !g_csFullPrinterName.IsEmpty()) { CString csTemp(pName); if (0 == g_csPartialPrinterName.Compare(csTemp)) { pName = g_csFullPrinterName.GetAnsi(); } } } CSTRING_CATCH { // Do nothing } return ORIGINAL_API(AddPrinterConnectionA)(pName); } /*++ This stub function verifies that DeletePrinterConnection is using a correct printer name. --*/ BOOL APIHOOK(DeletePrinterConnectionA)( LPSTR pName ) { CSTRING_TRY { if (pName && !g_csPartialPrinterName.IsEmpty() && !g_csFullPrinterName.IsEmpty()) { CString csTemp(pName); if (0 == g_csPartialPrinterName.Compare(csTemp)) { pName = g_csFullPrinterName.GetAnsi(); } } } CSTRING_CATCH { // Do nothing } return ORIGINAL_API(DeletePrinterConnectionA)(pName); } /*++ This stub function verifies that PrintDlgA is using a correct nFromPage and nToPage. --*/ BOOL APIHOOK(PrintDlgA)( LPPRINTDLG lppd ) { // check nFromPage and nToPage for legal values. if (lppd) { DPFN(eDbgLevelSpew, "[PrintDlgA] nFromPage = %d nToPage = %d", lppd->nFromPage, lppd->nToPage); if (lppd->nFromPage == 0xffff) { lppd->nFromPage = 0; DPFN( eDbgLevelInfo, "[PrintDlgA] Setting nFromPage to 0." ); } if (lppd->nToPage == 0xffff) { lppd->nToPage = lppd->nFromPage; DPFN( eDbgLevelInfo, "[PrintDlgA] Setting nToPage to %d.", lppd->nFromPage ); } } return ORIGINAL_API(PrintDlgA)(lppd); } /*++ Register hooked functions --*/ BOOL NOTIFY_FUNCTION( DWORD fdwReason) { if (fdwReason == DLL_PROCESS_ATTACH) { OSVERSIONINFOEX osvi; BOOL bOsVersionInfoEx; if (!InitializeCriticalSectionAndSpinCount(&g_critSec,0x80000000)) { return FALSE; } // Check to see if we are under win2k ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi); if(bOsVersionInfoEx) { if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 ) { g_bWin2k = TRUE; } } } return TRUE; } HOOK_BEGIN CALL_NOTIFY_FUNCTION APIHOOK_ENTRY(WINSPOOL.DRV, DocumentPropertiesA); APIHOOK_ENTRY(WINSPOOL.DRV, OpenPrinterA); APIHOOK_ENTRY(WINSPOOL.DRV, SetPrinterA); APIHOOK_ENTRY(WINSPOOL.DRV, EnumPrintersA); APIHOOK_ENTRY(WINSPOOL.DRV, OpenPrinterA); APIHOOK_ENTRY(WINSPOOL.DRV, DeviceCapabilitiesA); APIHOOK_ENTRY(WINSPOOL.DRV, AddPrinterConnectionA); APIHOOK_ENTRY(WINSPOOL.DRV, DeletePrinterConnectionA); APIHOOK_ENTRY(COMDLG32.DLL, PrintDlgA); APIHOOK_ENTRY(KERNEL32.DLL,GetProfileStringA); APIHOOK_ENTRY(GDI32.DLL, CreateDCA); APIHOOK_ENTRY(GDI32.DLL, ResetDCA); APIHOOK_ENTRY(GDI32.DLL, SetAbortProc); APIHOOK_ENTRY(GDI32.DLL, StartPage); HOOK_END IMPLEMENT_SHIM_END