/*++ Copyright (c) 1990 - 1996 Microsoft Corporation Module Name: printer.c Abstract: This module provides all the public exported APIs relating to Printer management for the Local Print Providor SplAddPrinter LocalAddPrinter SplDeletePrinter SplResetPrinter Author: Dave Snipp (DaveSn) 15-Mar-1991 Revision History: Matthew A Felton (Mattfe) 27-June-1994 Allow Multiple pIniSpoolers MattFe Jan5 Cleanup SplAddPrinter & UpdatePrinterIni Steve Wilson (NT) - Dec 1996 added DeleteThisKey --*/ #include #pragma hdrstop #include "clusspl.h" #define PRINTER_NO_CONTROL 0x00 extern WCHAR *szNull; WCHAR *szKMPrintersAreBlocked = L"KMPrintersAreBlocked"; WCHAR *szIniDevices = L"devices"; WCHAR *szIniPrinterPorts = L"PrinterPorts"; DWORD NetPrinterDecayPeriod = 1000*60*60; // 1 hour DWORD FirstAddNetPrinterTickCount = 0; extern GENERIC_MAPPING GenericMapping[SPOOLER_OBJECT_COUNT]; VOID FixDevModeDeviceName( LPWSTR pPrinterName, PDEVMODE pDevMode, DWORD cbDevMode ); VOID CheckAndUpdatePrinterRegAll( PINISPOOLER pIniSpooler, LPWSTR pszPrinterName, LPWSTR pszPort, BOOL bDelete ) { // Print Providers if they are simulating network connections // will have the Win.INI setting taken care of by the router // so don't do they update if they request it. if ( pIniSpooler->SpoolerFlags & SPL_UPDATE_WININI_DEVICES ) { UpdatePrinterRegAll( pszPrinterName, pszPort, bDelete ); } } DWORD ValidatePrinterAttributes( DWORD SourceAttributes, DWORD OriginalAttributes, LPWSTR pDatatype, LPBOOL pbValid, BOOL bSettableOnly ) /*++ Function Description: Validates the printer attributes to weed out incompatible settings Parameters: SourceAttributes - new attributes OriginalAttributes - old attributes pDatatype - default datatype on the printer pbValid - flag to indicate invalid combination of settings bSettableOnly - flag for SplAddPrinter Return Values: pbValid is set to TRUE if successful and new attributes are returned pbValid is set to FALSE otherwise and 0 is returned --*/ { // // Let only settable attributes be set, as well as the other bits that are already set in the printer. // DWORD TargetAttributes = (SourceAttributes & PRINTER_ATTRIBUTE_SETTABLE) | (OriginalAttributes & ~PRINTER_ATTRIBUTE_SETTABLE); if (pbValid) *pbValid = TRUE; // // If the printer is set to spool RAW only, the Default datatype should be a // ValidRawDatatype (RAW, RAW FF, ....) // if ((TargetAttributes & PRINTER_ATTRIBUTE_RAW_ONLY) && (pDatatype != NULL) && !ValidRawDatatype(pDatatype)) { if (pbValid) *pbValid = FALSE; SetLastError(ERROR_INVALID_DATATYPE); return 0; } // This is for use by SplAddPrinter() to let it set these attributes for a new printer if needed. if ( !bSettableOnly ) { if( SourceAttributes & PRINTER_ATTRIBUTE_LOCAL ) TargetAttributes |= PRINTER_ATTRIBUTE_LOCAL; /* Don't accept PRINTER_ATTRIBUTE_NETWORK * unless the PRINTER_ATTRIBUTE_LOCAL bit is set also. * This is a special case of a local printer masquerading * as a network printer. * Otherwise PRINTER_ATTRIBUTE_NETWORK should be set only * by win32spl. */ if( ( SourceAttributes & PRINTER_ATTRIBUTE_NETWORK ) &&( SourceAttributes & PRINTER_ATTRIBUTE_LOCAL ) ) TargetAttributes |= PRINTER_ATTRIBUTE_NETWORK; // // If it is a Fax Printer, set that bit. // if ( SourceAttributes & PRINTER_ATTRIBUTE_FAX ) TargetAttributes |= PRINTER_ATTRIBUTE_FAX; if ( SourceAttributes & PRINTER_ATTRIBUTE_TS ) TargetAttributes |= PRINTER_ATTRIBUTE_TS; } /* If both queued and direct, knock out direct: */ if((TargetAttributes & (PRINTER_ATTRIBUTE_QUEUED | PRINTER_ATTRIBUTE_DIRECT)) == (PRINTER_ATTRIBUTE_QUEUED | PRINTER_ATTRIBUTE_DIRECT)) { TargetAttributes &= ~PRINTER_ATTRIBUTE_DIRECT; } // // For direct printing the default data type must be RAW // if ((TargetAttributes & PRINTER_ATTRIBUTE_DIRECT) && (pDatatype != NULL) && !ValidRawDatatype(pDatatype)) { if (pbValid) *pbValid = FALSE; SetLastError(ERROR_INVALID_DATATYPE); return 0; } /* If both direct and keep-printed-jobs, knock out keep-printed-jobs */ if((TargetAttributes & (PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS | PRINTER_ATTRIBUTE_DIRECT)) == (PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS | PRINTER_ATTRIBUTE_DIRECT)) { TargetAttributes &= ~PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS; } return TargetAttributes; } BOOL CreatePrinterEntry( LPPRINTER_INFO_2 pPrinter, PINIPRINTER pIniPrinter, PBOOL pAccessSystemSecurity ) { BOOL bError = FALSE; if( !( pIniPrinter->pSecurityDescriptor = CreatePrinterSecurityDescriptor( pPrinter->pSecurityDescriptor ) )) { return FALSE; } *pAccessSystemSecurity = FALSE; pIniPrinter->signature = IP_SIGNATURE; pIniPrinter->pName = AllocSplStr(pPrinter->pPrinterName); if (!pIniPrinter->pName) { DBGMSG(DBG_WARNING, ("CreatePrinterEntry: Could not allocate PrinterName string\n" )); bError = TRUE; } if (pPrinter->pShareName) { pIniPrinter->pShareName = AllocSplStr(pPrinter->pShareName); if (!pIniPrinter->pShareName) { DBGMSG(DBG_WARNING, ("CreatePrinterEntry: Could not allocate ShareName string\n" )); bError = TRUE; } } else { pIniPrinter->pShareName = NULL; } if (pPrinter->pDatatype) { pIniPrinter->pDatatype = AllocSplStr(pPrinter->pDatatype); if (!pIniPrinter->pDatatype) { DBGMSG(DBG_WARNING, ("CreatePrinterEntry: Could not allocate Datatype string\n" )); bError = TRUE; } } else { #if DBG // // Error: the datatype should never be NULL // point. // SplLogEvent( pIniPrinter->pIniSpooler, LOG_ERROR, MSG_SHARE_FAILED, TRUE, L"CreatePrinterEntry", pIniPrinter->pName ? pIniPrinter->pName : L"(Nonep)", pIniPrinter->pShareName ? pIniPrinter->pShareName : L"(Nones)", L"NULL datatype", NULL ); #endif pIniPrinter->pDatatype = NULL; } // // If we have failed somewhere, clean up and exit. // if (bError) { FreeSplStr(pIniPrinter->pName); FreeSplStr(pIniPrinter->pShareName); FreeSplStr(pIniPrinter->pDatatype); return FALSE; } pIniPrinter->Priority = pPrinter->Priority ? pPrinter->Priority : DEF_PRIORITY; pIniPrinter->Attributes = ValidatePrinterAttributes(pPrinter->Attributes, pIniPrinter->Attributes, NULL, NULL, FALSE); pIniPrinter->StartTime = pPrinter->StartTime; pIniPrinter->UntilTime = pPrinter->UntilTime; pIniPrinter->pParameters = AllocSplStr(pPrinter->pParameters); pIniPrinter->pSepFile = AllocSplStr(pPrinter->pSepFile); pIniPrinter->pComment = AllocSplStr(pPrinter->pComment); pIniPrinter->pLocation = AllocSplStr(pPrinter->pLocation); if (pPrinter->pDevMode) { pIniPrinter->cbDevMode = pPrinter->pDevMode->dmSize + pPrinter->pDevMode->dmDriverExtra; SPLASSERT(pIniPrinter->cbDevMode); if (pIniPrinter->pDevMode = AllocSplMem(pIniPrinter->cbDevMode)) { // // This is OK because the pPrinter->pDevmode is validated to be // encapsulated in its Rpc buffer by the server. // memcpy(pIniPrinter->pDevMode, pPrinter->pDevMode, pIniPrinter->cbDevMode); FixDevModeDeviceName( pIniPrinter->pName, pIniPrinter->pDevMode, pIniPrinter->cbDevMode ); } } else { pIniPrinter->cbDevMode = 0; pIniPrinter->pDevMode = NULL; } pIniPrinter->DefaultPriority = pPrinter->DefaultPriority; pIniPrinter->pIniFirstJob = pIniPrinter->pIniLastJob = NULL; pIniPrinter->cJobs = pIniPrinter->AveragePPM = 0; pIniPrinter->GenerateOnClose = 0; // At present no API can set this up, the user has to use the // registry. LATER we should enhance the API to take this. pIniPrinter->pSpoolDir = NULL; // Initialize Status Information pIniPrinter->cTotalJobs = 0; pIniPrinter->cTotalBytes.LowPart = 0; pIniPrinter->cTotalBytes.HighPart = 0; GetSystemTime(&pIniPrinter->stUpTime); pIniPrinter->MaxcRef = 0; pIniPrinter->cTotalPagesPrinted = 0; pIniPrinter->cSpooling = 0; pIniPrinter->cMaxSpooling = 0; pIniPrinter->cErrorOutOfPaper = 0; pIniPrinter->cErrorNotReady = 0; pIniPrinter->cJobError = 0; pIniPrinter->DsKeyUpdate = 0; pIniPrinter->DsKeyUpdateForeground = 0; pIniPrinter->pszObjectGUID = NULL; pIniPrinter->pszCN = NULL; pIniPrinter->pszDN = NULL; // // Start from a Semi Random Number // That way if someone deletes and creates a printer of // the same name it is unlikely to have the same unique ID pIniPrinter->cChangeID = GetTickCount(); if (pIniPrinter->cChangeID == 0 ) pIniPrinter->cChangeID++; // // Initialize the masq printer cache, we just start with optimistic values // pIniPrinter->MasqCache.cJobs = 0; pIniPrinter->MasqCache.dwError = ERROR_SUCCESS; pIniPrinter->MasqCache.Status = 0; pIniPrinter->MasqCache.bThreadRunning = FALSE; return TRUE; } BOOL UpdateWinIni( PINIPRINTER pIniPrinter ) { PINIPORT pIniPort; DWORD i; BOOL bGenerateNetId = FALSE; LPWSTR pszPort; SplInSem(); if( !( pIniPrinter->pIniSpooler->SpoolerFlags & SPL_UPDATE_WININI_DEVICES )){ return TRUE; } // // Update win.ini for Win16 compatibility // if ( pIniPrinter->Status & PRINTER_PENDING_DELETION ) { CheckAndUpdatePrinterRegAll( pIniPrinter->pIniSpooler, pIniPrinter->pName, NULL, UPDATE_REG_DELETE ); } else { // // Initialize in case there are no ports that match this printer. // pszPort = szNullPort; for( pIniPort = pIniPrinter->pIniSpooler->pIniPort; pIniPort; pIniPort = pIniPort->pNext ){ for ( i = 0; i < pIniPort->cPrinters; i++ ) { if ( pIniPort->ppIniPrinter[i] == pIniPrinter ) { // // UpdatePrinterRegAll will automatically // convert "\\server\share" or ports with // spaces to Nexx: // pszPort = pIniPort->pName; break; } } } CheckAndUpdatePrinterRegAll( pIniPrinter->pIniSpooler, pIniPrinter->pName, pszPort, UPDATE_REG_CHANGE ); } BroadcastChange( pIniPrinter->pIniSpooler, WM_WININICHANGE, PR_JOBSTATUS, (LPARAM)szIniDevices); return TRUE; } BOOL DeletePrinterIni( PINIPRINTER pIniPrinter ) { DWORD Status; LPWSTR pSubkey; DWORD cbNeeded; LPWSTR pKeyName = NULL; HANDLE hToken; PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler; HKEY hPrinterKey; // // Only update if the spooler requests it. // if ((pIniSpooler->SpoolerFlags & SPL_NO_UPDATE_PRINTERINI) || !pIniPrinter->pName) { return TRUE; } hToken = RevertToPrinterSelf(); Status = hToken ? ERROR_SUCCESS : GetLastError(); if (!(pKeyName = SubChar(pIniPrinter->pName, L'\\', L','))) { Status = GetLastError(); goto error; } if ( Status == ERROR_SUCCESS ) { Status = SplRegOpenKey( pIniSpooler->hckPrinters, pKeyName, KEY_WRITE | KEY_READ | DELETE, &hPrinterKey, pIniSpooler ); } if (Status == ERROR_SUCCESS) { // Delete hPrinterKey - on success this returns ERROR_SUCCESS Status = SplDeleteThisKey( pIniSpooler->hckPrinters, hPrinterKey, pKeyName, TRUE, pIniSpooler ); if (Status != ERROR_SUCCESS) { DBGMSG(DBG_WARNING, ("DeletePrinterIni: DeleteThisKey returned %ld\n", Status )); } } // // If entries are in per h/w profile registries delete them. // DeletePrinterInAllConfigs(pIniPrinter); error: FreeSplStr(pKeyName); if ( hToken ) { if ( !ImpersonatePrinterClient(hToken) && Status == ERROR_SUCCESS ) { Status = GetLastError(); } } return (Status == ERROR_SUCCESS); } // // DeleteThisKey - returns ERROR_SUCCESS on final successful return // deletes a key from Registry // SWilson Dec 96 // DWORD SplDeleteThisKey( HKEY hParentKey, // handle to parent of key to delete HKEY hThisKey, // handle of key to delete LPWSTR pThisKeyName, // name of this key BOOL bDeleteNullKey, // if TRUE, then if pThisKeyName is NULL it is deleted PINISPOOLER pIniSpooler ) { DWORD dwResult = ERROR_SUCCESS, rc; WCHAR Name[MAX_PATH]; DWORD cchName; LPWSTR pName; HKEY hSubKey; // // If hThisKey is NULL , try to open it // if( hThisKey == NULL) { if((hParentKey != NULL) && ( pThisKeyName && *pThisKeyName ) ){ dwResult = SplRegOpenKey( hParentKey, pThisKeyName, KEY_WRITE | KEY_READ | DELETE, &hThisKey, pIniSpooler ); } } // // Exit if SplRegOpenKey failed or hParentKey or pThisKeyName are invalid // if( hThisKey == NULL ){ return dwResult; } // Get This key's children & delete them, then delete this key while(dwResult == ERROR_SUCCESS) { pName = Name; cchName = COUNTOF( Name ); dwResult = SplRegEnumKey( hThisKey, 0, pName, &cchName, NULL, pIniSpooler ); if (dwResult == ERROR_MORE_DATA) { SPLASSERT(cchName > MAX_PATH); if (!(pName = AllocSplMem(cchName * sizeof( *pName )))) { dwResult = GetLastError(); } else { dwResult = SplRegEnumKey( hThisKey, 0, pName, &cchName, NULL, pIniSpooler ); } } if (dwResult == ERROR_SUCCESS) { // SubKey found dwResult = SplRegCreateKey( hThisKey, // Open SubKey pName, 0, KEY_WRITE | KEY_READ | DELETE, NULL, &hSubKey, NULL, pIniSpooler); if (dwResult == ERROR_SUCCESS) { // Delete This SubKey dwResult = SplDeleteThisKey( hThisKey, hSubKey, pName, bDeleteNullKey, pIniSpooler ); } } if (pName != Name) FreeSplStr(pName); } rc = SplRegCloseKey(hThisKey, pIniSpooler); SPLASSERT(rc == ERROR_SUCCESS); if (dwResult == ERROR_NO_MORE_ITEMS) { // This Key has no children so can be deleted if ( (*pThisKeyName || bDeleteNullKey) && hParentKey != NULL ) { dwResult = SplRegDeleteKey(hParentKey, pThisKeyName, pIniSpooler); if (dwResult != ERROR_SUCCESS) { DBGMSG(DBG_WARNING, ("DeletePrinter: RegDeleteKey failed: %ld\n", dwResult)); } } else { dwResult = ERROR_SUCCESS; } } return dwResult; } BOOL PrinterCreateKey( HKEY hKey, LPWSTR pSubKey, PHKEY phkResult, PDWORD pdwLastError, PINISPOOLER pIniSpooler ) { BOOL bReturnValue; DWORD Status; Status = SplRegCreateKey( hKey, pSubKey, 0, KEY_READ | KEY_WRITE, NULL, phkResult, NULL, pIniSpooler ); if ( Status != ERROR_SUCCESS ) { DBGMSG( DBG_WARNING, ( "PrinterCreateKey: SplRegCreateKey %ws error %d\n", pSubKey, Status )); *pdwLastError = Status; bReturnValue = FALSE; } else { bReturnValue = TRUE; } return bReturnValue; } BOOL UpdatePrinterIni( PINIPRINTER pIniPrinter, DWORD dwChangeID ) { PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler; DWORD dwLastError = ERROR_SUCCESS; LPWSTR pKeyName = NULL; HANDLE hToken; DWORD dwTickCount; BOOL bReturnValue; DWORD cbData; DWORD cbNeeded; LPWSTR pszPorts; HANDLE hPrinterKey = NULL; HANDLE hBackUpPrinterKey = NULL; SplInSem(); // // Only update if the spooler requests it. // if( pIniSpooler->SpoolerFlags & SPL_NO_UPDATE_PRINTERINI ){ return TRUE; } try { hToken = RevertToPrinterSelf(); if ( hToken == FALSE ) { DBGMSG( DBG_TRACE, ("UpdatePrinterIni failed RevertToPrinterSelf %x\n", GetLastError() )); } pKeyName = SubChar(pIniPrinter->pName, L'\\', L','); if (!pKeyName) { dwLastError = GetLastError(); leave; } if ( !PrinterCreateKey( pIniSpooler->hckPrinters, pKeyName, &hPrinterKey, &dwLastError, pIniSpooler )) { leave; } if (dwChangeID == UPDATE_DS_ONLY) { RegSetDWord(hPrinterKey, szDsKeyUpdate, pIniPrinter->DsKeyUpdate, &dwLastError, pIniSpooler); RegSetDWord(hPrinterKey, szDsKeyUpdateForeground, pIniPrinter->DsKeyUpdateForeground, &dwLastError, pIniSpooler); leave; } if ( dwChangeID != KEEP_CHANGEID ) { // // WorkStation Caching requires a Unique ID so that they can quickly // tell if their Cache is up to date. // dwTickCount = GetTickCount(); // Ensure Uniqueness if ( dwTickCount == 0 ) dwTickCount++; if ( pIniPrinter->cChangeID == dwTickCount ) dwTickCount++; pIniPrinter->cChangeID = dwTickCount; RegSetDWord( hPrinterKey, szTimeLastChange, pIniPrinter->cChangeID, &dwLastError, pIniSpooler ); } if ( dwChangeID != CHANGEID_ONLY ) { RegSetDWord( hPrinterKey, szStatus, pIniPrinter->Status, &dwLastError, pIniSpooler ); RegSetString( hPrinterKey, szName, pIniPrinter->pName, &dwLastError, pIniSpooler ); if( hBackUpPrinterKey != NULL ){ RegSetString( hBackUpPrinterKey, szName, pIniPrinter->pName, &dwLastError, pIniSpooler ); } RegSetString( hPrinterKey, szShare, pIniPrinter->pShareName, &dwLastError, pIniSpooler ); RegSetString( hPrinterKey, szPrintProcessor, pIniPrinter->pIniPrintProc->pName, &dwLastError, pIniSpooler ); if ( !( pIniPrinter->Status & PRINTER_PENDING_DELETION )) { SPLASSERT( pIniPrinter->pDatatype != NULL ); } RegSetString( hPrinterKey, szDatatype, pIniPrinter->pDatatype, &dwLastError, pIniSpooler ); RegSetString( hPrinterKey, szParameters, pIniPrinter->pParameters, &dwLastError, pIniSpooler ); RegSetDWord( hPrinterKey, szAction, pIniPrinter->dwAction, &dwLastError, pIniSpooler ); RegSetString( hPrinterKey, szObjectGUID, pIniPrinter->pszObjectGUID, &dwLastError, pIniSpooler ); RegSetDWord( hPrinterKey, szDsKeyUpdate, pIniPrinter->DsKeyUpdate, &dwLastError, pIniSpooler); RegSetDWord( hPrinterKey, szDsKeyUpdateForeground, pIniPrinter->DsKeyUpdateForeground, &dwLastError, pIniSpooler); RegSetString( hPrinterKey, szDescription, pIniPrinter->pComment, &dwLastError, pIniSpooler ); RegSetString( hPrinterKey, szDriver, pIniPrinter->pIniDriver->pName, &dwLastError, pIniSpooler ); if( hBackUpPrinterKey != NULL ){ RegSetString( hBackUpPrinterKey, szDriver, pIniPrinter->pIniDriver->pName, &dwLastError, pIniSpooler ); } if (pIniPrinter->pDevMode) { cbData = pIniPrinter->cbDevMode; } else { cbData = 0; } RegSetBinaryData( hPrinterKey, szDevMode, (LPBYTE)pIniPrinter->pDevMode, cbData, &dwLastError, pIniSpooler ); if( hBackUpPrinterKey != NULL ){ RegSetBinaryData( hBackUpPrinterKey, szDevMode, (LPBYTE)pIniPrinter->pDevMode, cbData, &dwLastError, pIniSpooler ); } RegSetDWord( hPrinterKey, szPriority, pIniPrinter->Priority, &dwLastError, pIniSpooler ); RegSetDWord( hPrinterKey, szDefaultPriority, pIniPrinter->DefaultPriority, &dwLastError, pIniSpooler ); RegSetDWord(hPrinterKey, szStartTime, pIniPrinter->StartTime, &dwLastError, pIniSpooler ); RegSetDWord( hPrinterKey, szUntilTime, pIniPrinter->UntilTime, &dwLastError, pIniSpooler ); RegSetString( hPrinterKey, szSepFile, pIniPrinter->pSepFile, &dwLastError, pIniSpooler ); RegSetString( hPrinterKey, szLocation, pIniPrinter->pLocation, &dwLastError, pIniSpooler ); RegSetDWord( hPrinterKey, szAttributes, pIniPrinter->Attributes, &dwLastError, pIniSpooler ); RegSetDWord( hPrinterKey, szTXTimeout, pIniPrinter->txTimeout, &dwLastError, pIniSpooler ); RegSetDWord( hPrinterKey, szDNSTimeout, pIniPrinter->dnsTimeout, &dwLastError, pIniSpooler ); if (pIniPrinter->pSecurityDescriptor) { cbData = GetSecurityDescriptorLength( pIniPrinter->pSecurityDescriptor ); } else { cbData = 0; } RegSetBinaryData( hPrinterKey, szSecurity, pIniPrinter->pSecurityDescriptor, cbData, &dwLastError, pIniSpooler ); RegSetString( hPrinterKey, szSpoolDir, pIniPrinter->pSpoolDir, &dwLastError, pIniSpooler ); cbNeeded = 0; GetPrinterPorts( pIniPrinter, 0, &cbNeeded); if (!(pszPorts = AllocSplMem(cbNeeded))) { dwLastError = GetLastError(); leave; } GetPrinterPorts(pIniPrinter, pszPorts, &cbNeeded); RegSetString( hPrinterKey, szPort, pszPorts, &dwLastError, pIniSpooler ); if( hBackUpPrinterKey != NULL ){ RegSetString( hBackUpPrinterKey, szPort, pszPorts, &dwLastError, pIniSpooler ); } FreeSplMem(pszPorts); // // A Provider might want to Write Extra Data from Registry // if ( pIniSpooler->pfnWriteRegistryExtra != NULL ) { if ( !(*pIniSpooler->pfnWriteRegistryExtra)(pIniPrinter->pName, hPrinterKey, pIniPrinter->pExtraData)) { dwLastError = GetLastError(); } } if ( ( pIniPrinter->Status & PRINTER_PENDING_CREATION ) && ( dwLastError == ERROR_SUCCESS ) ) { pIniPrinter->Status &= ~PRINTER_PENDING_CREATION; RegSetDWord( hPrinterKey, szStatus, pIniPrinter->Status, &dwLastError, pIniSpooler ); } } } finally { if ( hPrinterKey ) SplRegCloseKey( hPrinterKey, pIniSpooler); if ( hBackUpPrinterKey ) SplRegCloseKey( hBackUpPrinterKey, pIniSpooler); if ( hToken ) ImpersonatePrinterClient( hToken ); } FreeSplStr(pKeyName); if ( dwLastError != ERROR_SUCCESS ) { SetLastError( dwLastError ); bReturnValue = FALSE; } else { bReturnValue = TRUE; } return bReturnValue; } VOID RemoveOldNetPrinters( PPRINTER_INFO_1 pPrinterInfo1, PINISPOOLER pIniSpooler ) { PININETPRINT *ppIniNetPrint = &pIniSpooler->pIniNetPrint; PININETPRINT pIniNetPrint; DWORD TickCount; TickCount = GetTickCount(); // // Browse Information only becomes valid after this print server has been // up for the NetPrinterDecayPeriod. // if (( bNetInfoReady == FALSE ) && (( TickCount - FirstAddNetPrinterTickCount ) > NetPrinterDecayPeriod )) { DBGMSG( DBG_TRACE, ("RemoveOldNetPrinters has a valid browse list\n" )); bNetInfoReady = TRUE; } while (*ppIniNetPrint) { // // If either the Tickcount has expired OR we want to delete this specific NetPrinter // ( because its no longer shared ). // if ( (( TickCount - (*ppIniNetPrint)->TickCount ) > NetPrinterDecayPeriod + TEN_MINUTES ) || ( pPrinterInfo1 != NULL && pPrinterInfo1->Flags & PRINTER_ATTRIBUTE_NETWORK && !(pPrinterInfo1->Flags & PRINTER_ATTRIBUTE_SHARED ) && _wcsicmp( pPrinterInfo1->pName, (*ppIniNetPrint)->pName ) == STRINGS_ARE_EQUAL)) { pIniNetPrint = *ppIniNetPrint; DBGMSG( DBG_TRACE, ("RemoveOldNetPrinters removing %ws not heard for %d millisconds\n", pIniNetPrint->pName, ( TickCount - (*ppIniNetPrint)->TickCount ) )); // // Remove this item, which also increments the pointer. // *ppIniNetPrint = pIniNetPrint->pNext; pIniSpooler->cNetPrinters--; FreeSplStr( pIniNetPrint->pName ); FreeSplStr( pIniNetPrint->pDescription ); FreeSplStr( pIniNetPrint->pComment ); FreeSplMem( pIniNetPrint ); } else { ppIniNetPrint = &(*ppIniNetPrint)->pNext; } } } HANDLE AddNetPrinter( LPBYTE pPrinterInfo, PINISPOOLER pIniSpooler ) /*++ Routine Description: Net Printers are created by remote machines calling AddPrinter( Level = 1, Printer_info_1 ) ( see server.c ). They are used for browsing, someone can call EnumPrinters and ask to get back our browse list - ie all our net printers. The printers in this list are decayed out after 1 hour ( default ). See return value comment. Note client\winspool.c AddPrinterW doesn't allow PRINTER_INFO_1 ( NET printers ), so this can only come from system components. Arguments: pPrinterInfo - Point to a PRINTER_INFO_1 structure to add Return Value: NULL - it doesn't return a printer handle. LastError = ERROR_SUCCESS, or error code ( like out of memory ). NOTE before NT 3.51 it returned a printer handle of type PRINTER_HANDLE_NET, but since the only use of this handle was to close it ( which burnt up cpu / net traffic and RPC binding handles, we return a NULL handle now to make it more efficient. Apps ( Server.c ) if it cares could call GetLastError. --*/ { PPRINTER_INFO_1 pPrinterInfo1 = (PPRINTER_INFO_1)pPrinterInfo; PININETPRINT pIniNetPrint = NULL; PININETPRINT *ppScan; SplInSem(); // // Validate PRINTER_INFO_1 // At minimum it must have a PrinterName. Each field in the pPrinterInfo1 // must be of an appropriate size. Description is PrinterName,DriverName,Location. // if ( pPrinterInfo1->pName == NULL || wcslen(pPrinterInfo1->pName) > MAX_UNC_PRINTER_NAME || (pPrinterInfo1->pComment && wcslen(pPrinterInfo1->pComment) > MAX_PATH) || (pPrinterInfo1->pDescription && wcslen(pPrinterInfo1->pDescription) > MAX_PATH + MAX_PATH + MAX_UNC_PRINTER_NAME + 2)) { DBGMSG( DBG_WARN, ("AddNetPrinter invalid printer parameters failed\n")); SetLastError( ERROR_INVALID_NAME ); return NULL; } if ( FirstAddNetPrinterTickCount == 0 ) { FirstAddNetPrinterTickCount = GetTickCount(); } // // Decay out of the browse list any old printers // RemoveOldNetPrinters( pPrinterInfo1, pIniSpooler ); // // Do Not Add and printer which is no longer shared. // if (pPrinterInfo1->Flags & PRINTER_ATTRIBUTE_NETWORK && !(pPrinterInfo1->Flags & PRINTER_ATTRIBUTE_SHARED)) { SetLastError(ERROR_PRINTER_ALREADY_EXISTS); goto Done; } // // See if we already have this printer // pIniNetPrint = pIniSpooler->pIniNetPrint; while ( pIniNetPrint && pIniNetPrint->pName && lstrcmpi( pPrinterInfo1->pName, pIniNetPrint->pName )) { pIniNetPrint = pIniNetPrint->pNext; } // // If we didn't find this printer already Create one // if (pIniNetPrint == NULL) { // // If we haven't already reached our maximum number of printers, then // go ahead and add this one. // if (pIniSpooler->cNetPrinters < kMaximumNumberOfBrowsePrinters) { pIniNetPrint = AllocSplMem( sizeof(ININETPRINT)); if (pIniNetPrint) { pIniNetPrint->signature = IN_SIGNATURE; pIniNetPrint->pName = AllocSplStr( pPrinterInfo1->pName ); pIniNetPrint->pDescription = AllocSplStr( pPrinterInfo1->pDescription ); pIniNetPrint->pComment = AllocSplStr( pPrinterInfo1->pComment ); // Did Any of the above allocations fail ? if ( pIniNetPrint->pName == NULL || ( pPrinterInfo1->pDescription != NULL && pIniNetPrint->pDescription == NULL ) || ( pPrinterInfo1->pComment != NULL && pIniNetPrint->pComment == NULL ) ) { // Failed - CleanUp FreeSplStr( pIniNetPrint->pComment ); FreeSplStr( pIniNetPrint->pDescription ); FreeSplStr( pIniNetPrint->pName ); FreeSplMem( pIniNetPrint ); pIniNetPrint = NULL; } else { DBGMSG( DBG_TRACE, ("AddNetPrinter(%ws) NEW\n", pPrinterInfo1->pName )); ppScan = &pIniSpooler->pIniNetPrint; // Scan through the current known printers, and insert the new one // in alphabetical order while( *ppScan && (lstrcmp((*ppScan)->pName, pIniNetPrint->pName) < 0)) { ppScan = &(*ppScan)->pNext; } pIniNetPrint->pNext = *ppScan; *ppScan = pIniNetPrint; pIniSpooler->cNetPrinters++; } } } else { SetLastError(ERROR_OUTOFMEMORY); } } else { DBGMSG( DBG_TRACE, ("AddNetPrinter(%ws) elapsed since last notified %d milliseconds\n", pIniNetPrint->pName, ( GetTickCount() - pIniNetPrint->TickCount ) )); } if ( pIniNetPrint ) { // Tickle the TickCount so this printer sticks around in the browse list pIniNetPrint->TickCount = GetTickCount(); // Have to set some error code or RPC thinks ERROR_SUCCESS is good. SetLastError( ERROR_PRINTER_ALREADY_EXISTS ); pIniSpooler->cAddNetPrinters++; // Status Only } Done: SPLASSERT( GetLastError() != ERROR_SUCCESS); return NULL; } /*++ Routine Name: ValidatePortTokenList Routine Description: This routine ensures that the given set of ports in pKeyData are valid ports in the spooler and returns the buffer with the pointers to strings replaced with pointers ref-counted pIniPorts. The way we do this needs to be rethought. The overloaded PKEYDATA is confusing and unnecessary, we should simply return a new array of PINIPORTS. (It also pollutes the PKEYDATA with an unnecessary bFixPortRef member), Also, this code is both invoked for initialization and for Validation, but the logic is quite different. For initialization, we want to assume that everything in the registry is valid and start up with placeholder ports until the monitor can enumerate them (this could be because a USB printer is unplugged). In the other cases where this is being used for validation we want to fail. This implies that we might want to separate this into two functions. Arguments: pKeyData - The array of strings that gets turned into an array of ref-counted ports. pIniSpooler - The ini-spooler on which this is being added. bInitialize - If TRUE, this code is being invoked for initialization and not for validation. pbNoPorts - Optional, this will return TRUE if bInitialize is TRUE and none of the ports in the port list can be found. We will then set the printer- offline and log a message. Return Value: TRUE - if the ports were all all successfully created or validated. FALSE - otherwise. --*/ BOOL ValidatePortTokenList( IN OUT PKEYDATA pKeyData, IN PINISPOOLER pIniSpooler, IN BOOL bInitialize, OUT BOOL *pbNoPorts OPTIONAL ) { PINIPORT pIniPort = NULL; DWORD i = 0; DWORD j = 0; DWORD dwPorts = 0; DWORD Status = ERROR_SUCCESS; SplInSem(); Status = !pKeyData ? ERROR_UNKNOWN_PORT : ERROR_SUCCESS; // // The logic remains the same for ports with only one token as for when we // initialize the ports for the first time. // if (Status == ERROR_SUCCESS) { bInitialize = pKeyData->cTokens == 1 ? TRUE : bInitialize; } // // We do not allow non-masc ports and masq ports to be combined. Moreover // only one non-masc port can be used for a printer -- can't do printer // pooling with masq printers // for ( i = 0 ; Status == ERROR_SUCCESS && i < pKeyData->cTokens ; i++ ) { pIniPort = FindPort(pKeyData->pTokens[i], pIniSpooler); // // A port is valid if it is found and if it isn't in itself a // placeholder port. // if (pIniPort && !(pIniPort->Status & PP_PLACEHOLDER)) { dwPorts++; } // // If we are initializing, or if there is only one port and if the // spooler allows it, then create a dummy port entry. This also // handles the masq port case. // if (bInitialize) { if (!pIniPort && pIniSpooler->SpoolerFlags & SPL_OPEN_CREATE_PORTS) { // // Note: there is a potential problem here, CreatePortEntry uses // a global initialization flag rather than the parameter that is // passed in to us. // pIniPort = CreatePortEntry(pKeyData->pTokens[i], NULL, pIniSpooler); } } // // If we don't have a port or if we are not initializing and there isn't // a monitor associated with the port. Then we have an error. // if (!pIniPort || (!(pIniPort->Status & PP_MONITOR) && !bInitialize)) { Status = ERROR_UNKNOWN_PORT; } // // In case of duplicate portnames in pPortName field fail the call. This // can't happen if we went through the CreatePortEntry code path and it // succeeded // for ( j = 0 ; Status == ERROR_SUCCESS && j < i ; ++j ) { if ( pIniPort == (PINIPORT)pKeyData->pTokens[j] ) { Status = ERROR_UNKNOWN_PORT; } } // // Write the port in. // if (Status == ERROR_SUCCESS) { pKeyData->pTokens[i] = (LPWSTR)pIniPort; } } // // If everything is successful, addref all of the pIniPorts and set the flag // to indicate that this has happened for the cleanup code. // if (Status == ERROR_SUCCESS) { for ( i = 0 ; i < pKeyData->cTokens ; ++i ) { pIniPort = (PINIPORT)pKeyData->pTokens[i]; INCPORTREF(pIniPort); } pKeyData->bFixPortRef = TRUE; } if (pbNoPorts) { *pbNoPorts = dwPorts == 0; } if (Status != ERROR_SUCCESS) { SetLastError(Status); } return Status == ERROR_SUCCESS; } DWORD ValidatePrinterName( LPWSTR pszNewName, PINISPOOLER pIniSpooler, PINIPRINTER pIniPrinter, LPWSTR *ppszLocalName ) /*++ Routine Description: Validates a printer name. Printer and share names exist in the same namespace, so validation is done against printer, share names. Arguments: pszNewName - printer name specified pIniSpooler - Spooler that owns printer pIniPrinter - could be null if the printer is getting created ppszLocalName - on success returns local name (\\servername stripped off if necessary). Return Value: DWORD error code. --*/ { PINIPRINTER pIniTempPrinter, pIniNextPrinter; LPWSTR pszLocalNameTmp = NULL; WCHAR string[MAX_UNC_PRINTER_NAME]; LPWSTR p; LPWSTR pLastSpace = NULL; // // The function ValidatePrinterName does too many things in one single routine. // It checks for validity of the printer name, it isolates the printer name, // it eliminates trailing white spaces from the printe name and validates // that the printer name is unique. // // The function IsValidPrinterName ensures that the printer names contains // valid characters and valid sequences of characters. // if (!IsValidPrinterName(pszNewName, MAX_UNC_PRINTER_NAME - 1)) { return ERROR_INVALID_PRINTER_NAME; } if (*pszNewName == L'\\' && *(pszNewName + 1) == L'\\') { p = wcschr(pszNewName + 2, L'\\'); if (p) { // // \\Server\Printer -> \\Server // StringCchCopy(string, (size_t) (p - pszNewName) + 1, pszNewName); if (MyName(string, pIniSpooler)) pszLocalNameTmp = p + 1; // \\Server\Printer -> \Printer } } if (!pszLocalNameTmp) pszLocalNameTmp = pszNewName; // // Strip trailing spaces. // for( p = pszLocalNameTmp; *p; ++p ){ if( *p == L' ' ){ // // If we haven't seen a continuous space, remember this // position. // if( !pLastSpace ){ pLastSpace = p; } } else { // // Non-whitespace. // pLastSpace = NULL; } } if( pLastSpace ){ *pLastSpace = 0; } // // Limit PrinterNames to MAX_PATH length, also if the printer name is now // empty as a result of stripping out all of the spaces, then the printer // name is now invalid. // if ( wcslen( pszLocalNameTmp ) > MAX_PRINTER_NAME || !*pszLocalNameTmp ) { return ERROR_INVALID_PRINTER_NAME; } // // Now validate that printer names are unique. Printer names and // share names reside in the same namespace (see net\dosprint\dosprtw.c). // for( pIniTempPrinter = pIniSpooler->pIniPrinter; pIniTempPrinter; pIniTempPrinter = pIniNextPrinter ){ // // Get the next printer now in case we delete the current // one in DeletePrinterCheck. // pIniNextPrinter = pIniTempPrinter->pNext; // // Skip ourselves, if we are passed in. // if( pIniTempPrinter == pIniPrinter ){ continue; } // // Disallow common Printer/Share names. // if( !lstrcmpi( pszLocalNameTmp, pIniTempPrinter->pName ) || ( pIniTempPrinter->Attributes & PRINTER_ATTRIBUTE_SHARED && !lstrcmpi( pszLocalNameTmp, pIniTempPrinter->pShareName ))){ if( !DeletePrinterCheck( pIniTempPrinter )){ return ERROR_PRINTER_ALREADY_EXISTS; } } } // // Success, now update ppszLocalName from pszLocalNameTmp. // *ppszLocalName = pszLocalNameTmp; return ERROR_SUCCESS; } DWORD ValidatePrinterShareName( LPWSTR pszNewShareName, PINISPOOLER pIniSpooler, PINIPRINTER pIniPrinter ) /*++ Routine Description: Validates the printer share name. Printer and share names exist in the same namespace, so validation is done against printer, share names. Arguments: pszNewShareName - share name specified pIniSpooler - Spooler that owns printer pIniPrinter - could be null if the printer is getting created Return Value: DWORD error code. --*/ { PINIPRINTER pIniTempPrinter, pIniNextPrinter; if ( !pszNewShareName || !*pszNewShareName || wcslen(pszNewShareName) > PATHLEN-1) { return ERROR_INVALID_SHARENAME; } // // Now validate that share names are unique. Share names and printer names // reside in the same namespace (see net\dosprint\dosprtw.c). // for( pIniTempPrinter = pIniSpooler->pIniPrinter; pIniTempPrinter; pIniTempPrinter = pIniNextPrinter ) { // // Get the next printer now in case we delete the current // one in DeletePrinterCheck. // pIniNextPrinter = pIniTempPrinter->pNext; // // Skip ourselves, if we are pssed in. // if( pIniTempPrinter == pIniPrinter ){ continue; } // // Check our share name now. // if( !lstrcmpi(pszNewShareName, pIniTempPrinter->pName) || ( pIniTempPrinter->Attributes & PRINTER_ATTRIBUTE_SHARED && !lstrcmpi(pszNewShareName, pIniTempPrinter->pShareName)) ) { if( !DeletePrinterCheck( pIniTempPrinter )){ return ERROR_INVALID_SHARENAME; } } } return ERROR_SUCCESS; } DWORD ValidatePrinterInfo( IN PPRINTER_INFO_2 pPrinter, IN PINISPOOLER pIniSpooler, IN PINIPRINTER pIniPrinter OPTIONAL, OUT LPWSTR* ppszLocalName OPTIONAL ) /*++ Routine Description: Validates that printer names/share do not collide. (Both printer and share names exist in the same namespace.) Note: Later, we should remove all this DeletePrinterCheck. As people decrement ref counts, they should DeletePrinterCheck themselves (or have it built into the decrement). Arguments: pPrinter - PrinterInfo2 structure to validate. pIniSpooler - Spooler that owns printer pIniPrinter - If printer already exists, don't check against itself. ppszLocalName - Returned pointer to string buffer in pPrinter; indicates local name (\\servername stripped off if necessary). Valid only on SUCCESS return code. Return Value: DWORD error code. --*/ { LPWSTR pszNewLocalName; DWORD dwLastError; if( !CheckSepFile( pPrinter->pSepFile )) { return ERROR_INVALID_SEPARATOR_FILE; } if( pPrinter->Priority != NO_PRIORITY && ( pPrinter->Priority > MAX_PRIORITY || pPrinter->Priority < MIN_PRIORITY )){ return ERROR_INVALID_PRIORITY; } if( pPrinter->StartTime >= ONEDAY || pPrinter->UntilTime >= ONEDAY){ return ERROR_INVALID_TIME; } if ( dwLastError = ValidatePrinterName(pPrinter->pPrinterName, pIniSpooler, pIniPrinter, &pszNewLocalName) ) { return dwLastError; } // Share name length validation if(pPrinter->pShareName && wcslen(pPrinter->pShareName) > PATHLEN-1){ return ERROR_INVALID_SHARENAME; } if ( pPrinter->Attributes & PRINTER_ATTRIBUTE_SHARED ){ if ( dwLastError = ValidatePrinterShareName(pPrinter->pShareName, pIniSpooler, pIniPrinter) ) { return dwLastError; } } // Server name length validation if ( pPrinter->pServerName && wcslen(pPrinter->pServerName) > MAX_PATH-1 ){ return ERROR_INVALID_PARAMETER; } // Comment length validation if ( pPrinter->pComment && wcslen(pPrinter->pComment) > PATHLEN-1 ){ return ERROR_INVALID_PARAMETER; } // Location length validation if ( pPrinter->pLocation && wcslen(pPrinter->pLocation) > MAX_PATH-1 ){ return ERROR_INVALID_PARAMETER; } // Parameters length validation if ( pPrinter->pParameters && wcslen(pPrinter->pParameters) > MAX_PATH-1){ return ERROR_INVALID_PARAMETER; } // Datatype length validation if ( pPrinter->pDatatype && wcslen(pPrinter->pDatatype) > MAX_PATH-1){ return ERROR_INVALID_DATATYPE; } if( ppszLocalName ){ *ppszLocalName = pszNewLocalName; } return ERROR_SUCCESS; } HANDLE LocalAddPrinter( LPWSTR pName, DWORD Level, LPBYTE pPrinterInfo ) { PINISPOOLER pIniSpooler; HANDLE hReturn; pIniSpooler = FindSpoolerByNameIncRef( pName, NULL ); if( !pIniSpooler ){ return ROUTER_UNKNOWN; } hReturn = SplAddPrinter( pName, Level, pPrinterInfo, pIniSpooler, NULL, NULL, 0); FindSpoolerByNameDecRef( pIniSpooler ); return hReturn; } HANDLE LocalAddPrinterEx( LPWSTR pName, DWORD Level, LPBYTE pPrinterInfo, LPBYTE pSplClientInfo, DWORD dwSplClientLevel ) { PINISPOOLER pIniSpooler; HANDLE hReturn; pIniSpooler = FindSpoolerByNameIncRef(pName, NULL); if( !pIniSpooler ){ return ROUTER_UNKNOWN; } hReturn = SplAddPrinter( pName, Level, pPrinterInfo, pIniSpooler, NULL, pSplClientInfo, dwSplClientLevel); FindSpoolerByNameDecRef( pIniSpooler ); return hReturn; } VOID RemovePrinterFromPort( IN PINIPRINTER pIniPrinter, IN PINIPORT pIniPort ) /*++ Routine Description: Remove a pIniPrinter structure from a pIniPort. Note: This code used to be inside RemovePrinterFromAllPorts. It searches the list of printers that uses the port for pIniPrinter. When it finds it, it adjusts the list of printers by moving the elements so that a failure of resizing the array won't affect the actual removing. RESIZEPORTPRINTERS will try to allocate a new buffer of given size. If succeeds the allocation, it will free the old buffer. If not, it will return NULL without freeing anything. Arguments: pIniPrinter - must not be NULL pIniPort - must not be NULL Return Value: VOID --*/ { DWORD j, k; PINIPRINTER *ppIniPrinter; SplInSem(); if(pIniPort && pIniPrinter) { for ( j = 0 ; j < pIniPort->cPrinters ; ++j ) { if ( pIniPort->ppIniPrinter[j] != pIniPrinter ) continue; // // Adjust the list of printers that use the port // for ( k = j + 1 ; k < pIniPort->cPrinters ; ++k ) pIniPort->ppIniPrinter[k-1] = pIniPort->ppIniPrinter[k]; ppIniPrinter = RESIZEPORTPRINTERS(pIniPort, -1); // // A memory allocation failure won't affect the actual removal // if ( ppIniPrinter != NULL ) pIniPort->ppIniPrinter = ppIniPrinter; if ( !--pIniPort->cPrinters ) RemoveDeviceName(pIniPort); break; } } } HANDLE SplAddPrinter( LPWSTR pName, DWORD Level, LPBYTE pPrinterInfo, PINISPOOLER pIniSpooler, LPBYTE pExtraData, LPBYTE pSplClientInfo, DWORD dwSplClientInfoLevel ) { PINIDRIVER pIniDriver = NULL; PINIPRINTPROC pIniPrintProc; PINIPRINTER pIniPrinter = NULL; PINIPORT pIniPort; PPRINTER_INFO_2 pPrinter=(PPRINTER_INFO_2)pPrinterInfo; DWORD cbIniPrinter = sizeof(INIPRINTER); BOOL bSucceeded = TRUE; PKEYDATA pKeyData = NULL; DWORD i; HANDLE hPrinter = NULL; DWORD TypeofHandle = PRINTER_HANDLE_PRINTER; PRINTER_DEFAULTS Defaults; PINIPORT pIniNetPort = NULL; PINIVERSION pIniVersion = NULL; HANDLE hPort = NULL; BOOL bAccessSystemSecurity = FALSE, bDriverEventCalled = FALSE; DWORD AccessRequested = 0; DWORD dwLastError = ERROR_SUCCESS; DWORD dwPrnEvntError = ERROR_SUCCESS; PDEVMODE pNewDevMode = NULL; PINIMONITOR pIniLangMonitor; LPWSTR pszDeviceInstanceId = NULL; // Quick Check Outside Critical Section // Since it is common for the ServerThread to call // AddPrinter Level 1, which we need to continue // to route to other Print Providers if (!MyName( pName, pIniSpooler )) { return FALSE; } try { EnterSplSem(); // PRINTER_INFO_1 is only used by printer browsing to replicate // data between different print servers. // Thus we add a Net printer for level 1. if ( Level == 1 ) { // // All network printers reside in pLocalIniSpooler to avoid // duplicates. // hPrinter = AddNetPrinter(pPrinterInfo, pLocalIniSpooler); leave; } if ( !ValidateObjectAccess(SPOOLER_OBJECT_SERVER, SERVER_ACCESS_ADMINISTER, NULL, NULL, pIniSpooler )) { leave; } if ( dwLastError = ValidatePrinterInfo( pPrinter, pIniSpooler, NULL, NULL )){ leave; } if (!(pKeyData = CreateTokenList(pPrinter->pPortName))) { dwLastError = ERROR_UNKNOWN_PORT; leave; } if ( pName && pName[0] ) { TypeofHandle |= PRINTER_HANDLE_REMOTE_DATA; } { HRESULT hRes = CheckLocalCall(); if (hRes == S_FALSE) { TypeofHandle |= PRINTER_HANDLE_REMOTE_CALL; } else if (hRes != S_OK) { dwLastError = SCODE_CODE(hRes); leave; } } if (!ValidatePortTokenList(pKeyData, pIniSpooler, FALSE, NULL)) { // // ValidatePortTokenList sets the last error to ERROR_INVALID_PRINTER_NAME // when port name is invalid. This is correct only for masquerading printers. // Otherwise, it should be ERROR_UNKNOWN_PORT. // Masq. Printer: both PRINTER_ATTRIBUTE_NETWORK | PRINTER_ATTRIBUTE_LOCAL are set. // if (!(pPrinter->Attributes & (PRINTER_ATTRIBUTE_NETWORK | PRINTER_ATTRIBUTE_LOCAL))) { SetLastError(ERROR_UNKNOWN_PORT); } leave; } FindLocalDriverAndVersion(pIniSpooler, pPrinter->pDriverName, &pIniDriver, &pIniVersion); if (!pIniDriver) { dwLastError = ERROR_UNKNOWN_PRINTER_DRIVER; leave; } // // Check for blocked KM drivers // if (KMPrintersAreBlocked() && IniDriverIsKMPD(pIniSpooler, FindEnvironment(szEnvironment, pIniSpooler), pIniVersion, pIniDriver)) { SplLogEvent( pIniSpooler, LOG_ERROR, MSG_KM_PRINTERS_BLOCKED, TRUE, pPrinter->pPrinterName, NULL ); dwLastError = ERROR_KM_DRIVER_BLOCKED; leave; } if (!(pIniPrintProc = FindPrintProc(pPrinter->pPrintProcessor, FindEnvironment(szEnvironment, pIniSpooler)))) { dwLastError = ERROR_UNKNOWN_PRINTPROCESSOR; leave; } if ( pPrinter->pDatatype && *pPrinter->pDatatype && !FindDatatype(pIniPrintProc, pPrinter->pDatatype) ) { dwLastError = ERROR_INVALID_DATATYPE; leave; } DBGMSG(DBG_TRACE, ("AddPrinter(%ws)\n", pPrinter->pPrinterName ? pPrinter->pPrinterName : L"NULL")); // // Set up defaults for CreatePrinterHandle. // If we create a printer we have Administer access to it: // Defaults.pDatatype = NULL; Defaults.pDevMode = NULL; Defaults.DesiredAccess = PRINTER_ALL_ACCESS; pIniPrinter = (PINIPRINTER)AllocSplMem( cbIniPrinter ); if ( pIniPrinter == NULL ) { leave; } pIniPrinter->signature = IP_SIGNATURE; pIniPrinter->Status |= PRINTER_PENDING_CREATION; pIniPrinter->pExtraData = pExtraData; pIniPrinter->pIniSpooler = pIniSpooler; pIniPrinter->dwPrivateFlag = 0; // Give the printer a unique session ID to pass around in notifications pIniPrinter->dwUniqueSessionID = dwUniquePrinterSessionID++; // // Reference count the pIniSpooler. // INCSPOOLERREF( pIniSpooler ); INCDRIVERREF(pIniDriver); pIniPrinter->pIniDriver = pIniDriver; pIniPrintProc->cRef++; pIniPrinter->pIniPrintProc = pIniPrintProc; pIniPrinter->dnsTimeout = DEFAULT_DNS_TIMEOUT; pIniPrinter->txTimeout = DEFAULT_TX_TIMEOUT; INCPRINTERREF( pIniPrinter ); if (!CreatePrinterEntry(pPrinter, pIniPrinter, &bAccessSystemSecurity)) { leave; } pIniPrinter->ppIniPorts = AllocSplMem(pKeyData->cTokens * sizeof(INIPORT)); if ( !pIniPrinter->ppIniPorts ) { leave; } if (!pIniPrinter->pDatatype) { pIniPrinter->pDatatype = AllocSplStr(*((LPWSTR *)pIniPrinter->pIniPrintProc->pDatatypes)); if ( pIniPrinter->pDatatype == NULL ) leave; } // Add this printer to the global list for this machine SplInSem(); pIniPrinter->pNext = pIniSpooler->pIniPrinter; pIniSpooler->pIniPrinter = pIniPrinter; // // When a printer is created we will enable bidi by default // pIniPrinter->Attributes &= ~PRINTER_ATTRIBUTE_ENABLE_BIDI; if ( pIniPrinter->pIniDriver->pIniLangMonitor ) { pIniPrinter->Attributes |= PRINTER_ATTRIBUTE_ENABLE_BIDI; } for ( i = 0; i < pKeyData->cTokens; i++ ) { pIniPort = (PINIPORT)pKeyData->pTokens[i]; if ( !AddIniPrinterToIniPort( pIniPort, pIniPrinter ) ) { leave; } pIniPrinter->ppIniPorts[i] = pIniPort; pIniPrinter->cPorts++; // If there isn't a monitor for this port, // it's a network printer. // Make sure we can get a handle for it. // This will attempt to open only the first one // it finds. Any others will be ignored. if (!(pIniPort->Status & PP_MONITOR) && !hPort) { if(bSucceeded = OpenPrinterPortW(pIniPort->pName, &hPort, NULL)) { // Store the address of the INIPORT structure // that refers to the network share. // This should correspond to pIniPort in any // handles opened on this printer. // Only the first INIPORT in the linked list // is a valid network port. pIniNetPort = pIniPort; pIniPrinter->pIniNetPort = pIniNetPort; // // Clear the placeholder status from the pIniPort. // pIniPort->Status &= ~PP_PLACEHOLDER; } else { DBGMSG(DBG_WARNING, ("SplAddPrinter OpenPrinterPort( %ws ) failed: Error %d\n", pIniPort->pName, GetLastError())); leave; } } else if (!pIniPort->hMonitorHandle) { LPTSTR pszPrinter; TCHAR szFullPrinter[ MAX_UNC_PRINTER_NAME ]; if ( pIniPrinter->Attributes & PRINTER_ATTRIBUTE_ENABLE_BIDI ) pIniLangMonitor = pIniPrinter->pIniDriver->pIniLangMonitor; else pIniLangMonitor = NULL; if( pIniPrinter->pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER ){ pszPrinter = szFullPrinter; if (!BoolFromHResult(StringCchPrintf(szFullPrinter, COUNTOF(szFullPrinter), L"%ws\\%ws", pIniSpooler->pMachineName, pIniPrinter->pName))){ leave; } } else { pszPrinter = pIniPrinter->pName; } OpenMonitorPort(pIniPort, pIniLangMonitor, pszPrinter); ReleaseMonitorPort(pIniPort); } } if ( !UpdateWinIni( pIniPrinter ) ) { leave; } if (bAccessSystemSecurity) { Defaults.DesiredAccess |= ACCESS_SYSTEM_SECURITY; } AccessRequested = Defaults.DesiredAccess; SplInSem(); hPrinter = CreatePrinterHandle( pIniPrinter->pName, pName ? pName : pIniSpooler->pMachineName, pIniPrinter, pIniPort, pIniNetPort, NULL, TypeofHandle, hPort, &Defaults, pIniSpooler, AccessRequested, pSplClientInfo, dwSplClientInfoLevel, INVALID_HANDLE_VALUE ); if ( hPrinter == NULL ) { leave; } if ( !UpdatePrinterIni( pIniPrinter, UPDATE_CHANGEID )) { dwLastError = GetLastError(); SplClosePrinter( hPrinter ); hPrinter = NULL; leave; } if ( pIniPrinter->Attributes & PRINTER_ATTRIBUTE_SHARED ) { INC_PRINTER_ZOMBIE_REF(pIniPrinter); // // NOTE ShareThisPrinter will leave critical section and the // server will call the spooler back again to OpenPrinter this // printer. So this printer MUST be fully created at the point // it is shared, so that Open can succeed. // bSucceeded = ShareThisPrinter(pIniPrinter, pIniPrinter->pShareName, TRUE ); DEC_PRINTER_ZOMBIE_REF(pIniPrinter); if ( !bSucceeded ) { // // We do not want to delete the existing share in DeletePrinterIni // pIniPrinter->Attributes &= ~PRINTER_ATTRIBUTE_SHARED; DBGMSG( DBG_WARNING, ("LocalAddPrinter: %ws share failed %ws error %d\n", pIniPrinter->pName, pIniPrinter->pShareName, GetLastError() )); // // With PRINTER_PENDING_CREATION turned on we will Delete this printer. // pIniPrinter->Status |= PRINTER_PENDING_CREATION; dwLastError = GetLastError(); SPLASSERT( hPrinter ); SplClosePrinter( hPrinter ); hPrinter = NULL; leave; } } pIniPrinter->Status |= PRINTER_OK; SplInSem(); // Call the DriverEvent with PRINTER_INITIALIZE while adding local printers LeaveSplSem(); if (pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL) { if (bDriverEventCalled = PrinterDriverEvent(pIniPrinter, PRINTER_EVENT_INITIALIZE, (LPARAM)NULL, &dwPrnEvntError) == FALSE) { dwLastError = dwPrnEvntError; if (dwLastError != ERROR_PROC_NOT_FOUND) { if (!dwLastError) dwLastError = ERROR_CAN_NOT_COMPLETE; EnterSplSem(); // // With PRINTER_PENDING_CREATION turned on the printer will be deleted. // pIniPrinter->Status |= PRINTER_PENDING_CREATION; SplClosePrinter( hPrinter ); hPrinter = NULL; leave; } } } EnterSplSem(); // // If no devmode is given get driver default, if a devmode is given // convert it to current version // // Check if it's local (either pLocalIniSpooler or cluster). // if ( pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL ) { if (!(pNewDevMode = ConvertDevModeToSpecifiedVersion(pIniPrinter, pIniPrinter->pDevMode, NULL, NULL, CURRENT_VERSION))) { dwLastError = GetLastError(); if (!dwLastError) { dwLastError = ERROR_CAN_NOT_COMPLETE; } // // With PRINTER_PENDING_CREATION turned on the printer will be deleted. // pIniPrinter->Status |= PRINTER_PENDING_CREATION; SplClosePrinter( hPrinter ); hPrinter = NULL; leave; } // // If call is remote we must convert devmode before setting it // if ( pNewDevMode || (TypeofHandle & PRINTER_HANDLE_REMOTE_DATA) ) { FreeSplMem(pIniPrinter->pDevMode); pIniPrinter->pDevMode = pNewDevMode; if ( pNewDevMode ) { pIniPrinter->cbDevMode = pNewDevMode->dmSize + pNewDevMode->dmDriverExtra; SPLASSERT(pIniPrinter->cbDevMode); } else { pIniPrinter->cbDevMode = 0; } pNewDevMode = NULL; } } if ( pIniPrinter->pDevMode ) { // // Fix up the DEVMODE.dmDeviceName field. // FixDevModeDeviceName(pIniPrinter->pName, pIniPrinter->pDevMode, pIniPrinter->cbDevMode); } // // We need to write the new devmode to the registry // if (!UpdatePrinterIni(pIniPrinter, UPDATE_CHANGEID)) { DBGMSG(DBG_WARNING, ("SplAddPrinter: UpdatePrinterIni failed after devmode conversion\n")); } // // For Masq printers, give the provider a chance to update the printer registry, if desired. // if( pIniNetPort ) { static const WCHAR c_szHttp[] = L"http://"; static const WCHAR c_szHttps[] = L"https://"; if( !_wcsnicmp( pIniPort->pName, c_szHttp, lstrlen ( c_szHttp ) ) || !_wcsnicmp( pIniPort->pName, c_szHttps, lstrlen ( c_szHttps ) ) ) { UpdatePrinterNetworkName(pIniPrinter, pIniPort->pName); } } // // DS: Create the DS keys // INCPRINTERREF(pIniPrinter); LeaveSplSem(); RecreateDsKey(hPrinter, SPLDS_DRIVER_KEY); RecreateDsKey(hPrinter, SPLDS_SPOOLER_KEY); EnterSplSem(); DECPRINTERREF(pIniPrinter); // // From this point on we don't care if any of these fail // we still keep the created printer. // SplLogEvent( pIniSpooler, LOG_INFO, MSG_PRINTER_CREATED, TRUE, pIniPrinter->pName, NULL ); SetPrinterChange(pIniPrinter, NULL, NVPrinterAll, PRINTER_CHANGE_ADD_PRINTER, pIniSpooler); } finally { SplInSem(); if ( hPrinter == NULL ) { // FAILURE CLEAN-UP // If a subroutine we called failed // then we should save its error incase it is // altered during cleanup. if ( dwLastError == ERROR_SUCCESS ) { dwLastError = GetLastError(); } if ( pIniPrinter == NULL ) { // Allow a Print Provider to free its ExtraData // associated with this printer. if (( pIniSpooler->pfnFreePrinterExtra != NULL ) && ( pExtraData != NULL )) { (*pIniSpooler->pfnFreePrinterExtra)( pExtraData ); } } else if ( pIniPrinter->Status & PRINTER_PENDING_CREATION ) { if (bDriverEventCalled) { LeaveSplSem(); // Call Driver Event to report that the printer has been deleted PrinterDriverEvent( pIniPrinter, PRINTER_EVENT_DELETE, (LPARAM)NULL, &dwPrnEvntError ); EnterSplSem(); } DECPRINTERREF( pIniPrinter ); InternalDeletePrinter( pIniPrinter ); } } else { // Success if ( pIniPrinter ) { DECPRINTERREF( pIniPrinter ); } } FreePortTokenList(pKeyData); LeaveSplSem(); SplOutSem(); FreeSplMem(pNewDevMode); if ( hPrinter == NULL && Level != 1 ) { DBGMSG(DBG_WARNING, ("SplAddPrinter failed error %d\n", dwLastError )); SPLASSERT(dwLastError); SetLastError ( dwLastError ); } DBGMSG( DBG_TRACE, ("SplAddPrinter returned handle %x\n", hPrinter )); } // // Make (HANDLE)-1 indicate ROUTER_STOP_ROUTING. // if( !hPrinter ){ hPrinter = (HANDLE)-1; } return hPrinter; } VOID RemovePrinterFromAllPorts( IN PINIPRINTER pIniPrinter, IN BOOL bIsInitTime ) /*++ Routine Description: Remove a pIniPrinter structure from all pIniPort structures that it is associated with. Note: This code used to be inside RemovePrinterFromAllPorts. It searches the list of printers that uses the port for pIniPrinter. When it finds it, it adjust the list of printers by moving the elements so that a failure of resizing the array won't affect the actual removing. RESIZEPORTPRINTERS will try to allocate a new buffer of given size. If succeeds the allocation, it will free the old buffer. If not, it will return NULL without freeing anything. Arguments: pIniPrinter - must not be NULL pIniPort - must not be NULL Return Value: VOID --*/ { DWORD i,j, k; PINIPORT pIniPort; PINIPRINTER *ppIniPrinter; SplInSem(); for ( i = 0 ; i < pIniPrinter->cPorts ; ++i ) { pIniPort = pIniPrinter->ppIniPorts[i]; RemovePrinterFromPort(pIniPrinter, pIniPort); // // Delete port if is initialization time , it doesn't have printers // attached and it has no Monitor; // This is a fix for the USBMON problem described below: // USBMON doesn't enumerate the ports which are not used by a printer. // Spooler doesn't enumerate printers which are in pending deletion state. // Scenario: Spooler initializes , all printers that uses a // certain USB_X port are in pending deletion state, // the USB_X port doesn't get enumerated by USBMON, // but it is created as a fake port by spooler since is still used by // the printer in pending deletion state. Eventually the prinetr goes away, // but we end up with this fake port. // if( bIsInitTime && !pIniPort->cPrinters && !pIniPort->pIniMonitor ) DeletePortEntry(pIniPort); } } VOID CloseMonitorsRestartOrphanJobs( PINIPRINTER pIniPrinter ) { PINIPORT pIniPort; DWORD i; BOOL bFound; SplInSem(); for ( pIniPort = pIniPrinter->pIniSpooler->pIniPort; pIniPort != NULL; pIniPort = pIniPort->pNext ) { if ( pIniPort->pIniJob != NULL && pIniPort->pIniJob->pIniPrinter == pIniPrinter ) { // // If this printer is no longer associated with this port // then restart that job. // for ( i = 0, bFound = FALSE; i < pIniPort->cPrinters; i++) { if (pIniPort->ppIniPrinter[i] == pIniPrinter) { bFound = TRUE; } } if ( !bFound ) { DBGMSG( DBG_WARNING, ("CloseMonitorsRestartOrphanJobs Restarting JobId %d\n", pIniPort->pIniJob->JobId )); RestartJob( pIniPort->pIniJob ); } } if ( !pIniPort->cPrinters && !(pIniPort->Status & PP_THREADRUNNING) ) { CloseMonitorPort(pIniPort); } } } // // This really does delete the printer. // It should be called only when the printer has no open handles // and no jobs waiting to print // BOOL DeletePrinterForReal( PINIPRINTER pIniPrinter, BOOL bIsInitTime ) { PINIPRINTER *ppIniPrinter; DWORD i,j; PINISPOOLER pIniSpooler; LPWSTR pComma; DWORD Status; SplInSem(); SPLASSERT( pIniPrinter->pIniSpooler->signature == ISP_SIGNATURE ); pIniSpooler = pIniPrinter->pIniSpooler; if ( pIniPrinter->pName != NULL ) { DBGMSG( DBG_TRACE, ("Deleting %ws for real\n", pIniPrinter->pName )); } CheckAndUpdatePrinterRegAll( pIniSpooler, pIniPrinter->pName, NULL, UPDATE_REG_DELETE ); DeleteIniPrinterDevNode(pIniPrinter); DeletePrinterIni( pIniPrinter ); // Take this IniPrinter off the list of printers for // this IniSpooler SplInSem(); ppIniPrinter = &pIniSpooler->pIniPrinter; while (*ppIniPrinter && *ppIniPrinter != pIniPrinter) { ppIniPrinter = &(*ppIniPrinter)->pNext; } if (*ppIniPrinter) *ppIniPrinter = pIniPrinter->pNext; // // Decrement useage counts for Print Processor & Driver // if ( pIniPrinter->pIniPrintProc ) pIniPrinter->pIniPrintProc->cRef--; if ( pIniPrinter->pIniDriver ) DECDRIVERREF(pIniPrinter->pIniDriver); RemovePrinterFromAllPorts(pIniPrinter, bIsInitTime); CloseMonitorsRestartOrphanJobs( pIniPrinter ); DeletePrinterSecurity( pIniPrinter ); // When the printer is Zombied it gets a trailing comma // Concatingated with the name ( see job.c deleteprintercheck ). // Remove trailing , from printer name before we log it as deleted. if ( pIniPrinter->pName != NULL ) { pComma = wcsrchr( pIniPrinter->pName, *szComma ); if ( pComma != NULL ) { *pComma = 0; } SplLogEvent( pIniSpooler, LOG_WARNING, MSG_PRINTER_DELETED, TRUE, pIniPrinter->pName, NULL ); } // Deleting the network port if it exists if (pIniPrinter->pIniNetPort && pIniPrinter->pIniNetPort->pName) { DeleteIniNetPort(pIniPrinter); SplInSem(); } FreeStructurePointers((LPBYTE) pIniPrinter, NULL, IniPrinterOffsets); // // Allow a Print Provider to free its ExtraData // associated with this printer. // if (( pIniSpooler->pfnFreePrinterExtra != NULL ) && ( pIniPrinter->pExtraData != NULL )) { (*pIniSpooler->pfnFreePrinterExtra)( pIniPrinter->pExtraData ); } // // Reference count the pIniSpooler. // DECSPOOLERREF( pIniPrinter->pIniSpooler ); FreeSplMem( pIniPrinter ); return TRUE; } VOID DeleteIniNetPort( PINIPRINTER pIniPrinter ) { LPWSTR pszName = NULL; LPWSTR pszPortName = NULL; HANDLE hXcv = NULL; PRINTER_DEFAULTS Defaults = { NULL, NULL, SERVER_ACCESS_ADMINISTER }; static const TCHAR pszPrefix[] = TEXT(",XcvPort "); BOOL bUsingXcvData = FALSE; DWORD dwNeeded, dwStatus, dwSize; SplInSem(); pszPortName = AllocSplStr(pIniPrinter->pIniNetPort->pName); if (pszPortName) { LeaveSplSem(); SplOutSem(); if (ERROR_SUCCESS == StrCatAlloc(&pszName, pszPrefix, pszPortName, NULL)) { // // We try opening a transcieve handle to the port. // bUsingXcvData = OpenPrinterPortW(pszName, &hXcv, &Defaults); if (bUsingXcvData) { dwSize = (lstrlen(pszPortName) + 1)*sizeof(TCHAR); bUsingXcvData = XcvData(hXcv, TEXT("DeletePort"), (LPBYTE)pszPortName, dwSize, NULL, 0, &dwNeeded, &dwStatus); } // If we fail to use XcvData to delete the port we try to do it using DeletePort. if (!bUsingXcvData) { DeletePort(NULL, NULL, pszName); } if (hXcv) { ClosePrinter(hXcv); } } FreeSplMem(pszName); FreeSplStr(pszPortName); EnterSplSem(); } } VOID InternalDeletePrinter( PINIPRINTER pIniPrinter ) { BOOL dwRet = FALSE; DWORD dwPrnEvntError = ERROR_SUCCESS; SPLASSERT( pIniPrinter->signature == IP_SIGNATURE ); SPLASSERT( pIniPrinter->pIniSpooler->signature == ISP_SIGNATURE ); // // This Might be a partially created printer that has no name // if ( pIniPrinter->pName != NULL ) { DBGMSG(DBG_TRACE, ("LocalDeletePrinter: %ws pending deletion: references = %d; jobs = %d\n", pIniPrinter->pName, pIniPrinter->cRef, pIniPrinter->cJobs)); INCPRINTERREF( pIniPrinter ); SplLogEvent( pIniPrinter->pIniSpooler, LOG_WARNING, MSG_PRINTER_DELETION_PENDING, TRUE, pIniPrinter->pName, NULL ); DECPRINTERREF( pIniPrinter ); } // // Mark the printer as "Don't accept any jobs" to make sure // that no more are accepted while we are outside CS. // Marking the printer in PRINTER_PENDING_DELETION also would // prevent adding any jobs, but then the OpenPrinter calls that the // driver does inside DrvDriverEvent will fail. // pIniPrinter->Status |= PRINTER_NO_MORE_JOBS; if (pIniPrinter->cJobs == 0) { INCPRINTERREF(pIniPrinter); LeaveSplSem(); SplOutSem(); PrinterDriverEvent( pIniPrinter, PRINTER_EVENT_DELETE, (LPARAM)NULL, &dwPrnEvntError ); EnterSplSem(); SplInSem(); DECPRINTERREF(pIniPrinter); } pIniPrinter->Status |= PRINTER_PENDING_DELETION; if (!(pIniPrinter->Status & PRINTER_PENDING_CREATION)) { SetPrinterChange(pIniPrinter, NULL, NVPrinterStatus, PRINTER_CHANGE_DELETE_PRINTER, pIniPrinter->pIniSpooler ); } INC_PRINTER_ZOMBIE_REF( pIniPrinter ); if ( pIniPrinter->Attributes & PRINTER_ATTRIBUTE_SHARED ) { dwRet = ShareThisPrinter(pIniPrinter, pIniPrinter->pShareName, FALSE); if (!dwRet) { pIniPrinter->Attributes &= ~PRINTER_ATTRIBUTE_SHARED; pIniPrinter->Status |= PRINTER_WAS_SHARED; CreateServerThread(); } else { DBGMSG(DBG_WARNING, ("LocalDeletePrinter: Unsharing this printer failed %ws\n", pIniPrinter->pName)); } } DEC_PRINTER_ZOMBIE_REF( pIniPrinter ); // // The printer doesn't get deleted until ClosePrinter is called // on the last remaining handle. // UpdatePrinterIni( pIniPrinter, UPDATE_CHANGEID ); UpdateWinIni( pIniPrinter ); DeletePrinterCheck( pIniPrinter ); } BOOL SplDeletePrinter( HANDLE hPrinter ) { PINIPRINTER pIniPrinter; PSPOOL pSpool = (PSPOOL)hPrinter; DWORD LastError = ERROR_SUCCESS; PINISPOOLER pIniSpooler; EnterSplSem(); pIniSpooler = pSpool->pIniSpooler; SPLASSERT( pIniSpooler->signature == ISP_SIGNATURE ); if ( ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER) ) { pIniPrinter = pSpool->pIniPrinter; DBGMSG( DBG_TRACE, ( "SplDeletePrinter: %s called\n", pIniPrinter->pName )); if ( !AccessGranted(SPOOLER_OBJECT_PRINTER, DELETE, pSpool) ) { LastError = ERROR_ACCESS_DENIED; } else if (pIniPrinter->cJobs && (pIniPrinter->Status & PRINTER_PAUSED)) { // Don't allow a printer to be deleted that is paused and has // jobs waiting, otherwise it'll never get deleted: LastError = ERROR_PRINTER_HAS_JOBS_QUEUED; } else { if (!(pIniPrinter->pIniSpooler->SpoolerFlags & SPL_TYPE_CACHE) && (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_PUBLISHED)) { if (!pIniPrinter->bDsPendingDeletion) { pIniPrinter->bDsPendingDeletion = TRUE; INCPRINTERREF(pIniPrinter); // DECPRINTERREF is done in UnpublishByGUID SetPrinterDs(hPrinter, DSPRINT_UNPUBLISH, FALSE); } } InternalDeletePrinter( pIniPrinter ); (VOID) ObjectDeleteAuditAlarm( szSpooler, pSpool, pSpool->GenerateOnClose ); } } else LastError = ERROR_INVALID_HANDLE; LeaveSplSem(); SplOutSem(); if (LastError) { SetLastError(LastError); return FALSE; } return TRUE; } BOOL PurgePrinter( PINIPRINTER pIniPrinter ) { PINIJOB pIniJob, pIniNextJob; PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler; SplInSem(); pIniNextJob = pIniPrinter->pIniFirstJob; // // We go through the list of all the jobs for the printer and delete them. // The check for cRef == 0 or JOB_PENDING_DELETION is for optimization. // We keep a refcount on the next job so that it doesnt get deleted when // we leave the CS during DeleteJob. Any Jobs with status Spooling are just // ignored because the spooler does not support deleting a job which is // being spooled, as yet. // while (pIniNextJob) { pIniJob = pIniNextJob; pIniNextJob = pIniJob->pIniNextJob; if ( (pIniJob->cRef == 0) || !(pIniJob->Status & JOB_PENDING_DELETION)) { // this job is going to be deleted DBGMSG(DBG_TRACE, ("Job Address 0x%.8x Job Status 0x%.8x\n", pIniJob, pIniJob->Status)); InterlockedAnd((LONG*)&(pIniJob->Status), ~JOB_RESTART); if (pIniNextJob) { INCJOBREF(pIniNextJob); } DeleteJob(pIniJob,NO_BROADCAST); if (pIniNextJob) { DECJOBREF(pIniNextJob); } } } // When purging a printer we don't want to generate a spooler information // message for each job being deleted becuase a printer might have a very // large number of jobs being purged would lead to a large number of // of unnessary and time consuming messages being generated. // Since this is a information only message it shouldn't cause any problems // Also Win 3.1 didn't have purge printer functionality and the printman // generated this message on Win 3.1 if (dwEnableBroadcastSpoolerStatus) { BroadcastChange( pIniSpooler,WM_SPOOLERSTATUS, PR_JOBSTATUS, (LPARAM)0); } return TRUE; } BOOL SetPrinterPorts( PSPOOL pSpool, // Caller's printer handle. May be NULL. PINIPRINTER pIniPrinter, PKEYDATA pKeyData ) { DWORD i,j; PINIPORT pIniNetPort = NULL, pIniPort; BOOL bReturnValue = TRUE; PINIPRINTER *ppIniPrinter; SPLASSERT( pIniPrinter != NULL ); SPLASSERT( pIniPrinter->signature == IP_SIGNATURE ); SPLASSERT( pIniPrinter->pIniSpooler != NULL ); SPLASSERT( pIniPrinter->pIniSpooler->signature == ISP_SIGNATURE ); // // Can't change the port for a masq printer // if ( (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_LOCAL) && (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_NETWORK) ) { if ( pKeyData->cTokens == 1 && pSpool->pIniNetPort == (PINIPORT)pKeyData->pTokens[0] ) return TRUE; SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // // Can't change printer port to that of a masq printer // for ( i = 0 ; i < pKeyData->cTokens ; ++i ) if ( !(((PINIPORT) pKeyData->pTokens[i])->Status & PP_MONITOR) ) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // // Remove the printer from all ports ; break the link from ports to printer // RemovePrinterFromAllPorts(pIniPrinter, NON_INIT_TIME); // // Remove all ports from printer; break the link from printer to ports // FreeSplMem(pIniPrinter->ppIniPorts); pIniPrinter->ppIniPorts = NULL; pIniPrinter->cPorts = 0; // // If we fail to add all the ports inside pKeyData, we'll leave the printer in this state // where it's initials ports are gone and only part or none of the ports are added. // // Go through all the ports that this printer is connected to, // and add build the bi-dir links between printer and ports. for (i = 0; i < pKeyData->cTokens; i++ ) { pIniPort = (PINIPORT)pKeyData->pTokens[i]; // // Add pIniPrinter to pIniPort // if ( AddIniPrinterToIniPort( pIniPort, pIniPrinter ) ) { // // If we succeeded, add pIniPort to pIniPrinter // if ( !AddIniPortToIniPrinter( pIniPrinter, pIniPort ) ) { // // If fail, then remove pIniPrinter from pIniPort ; // If we don't do this, pIniPort will point to invalid memory when piniPrinter gets deleted // RemovePrinterFromPort(pIniPrinter, pIniPort); bReturnValue = FALSE; goto Cleanup; } } else { bReturnValue = FALSE; goto Cleanup; } } CloseMonitorsRestartOrphanJobs( pIniPrinter ); Cleanup: return bReturnValue; } /*++ Routine Name: AllocResetDevMode Routine Description: This routine makes a copy of the passed in devmode that can be associated to the printer handle in the call to reset printer. The passed in devmode can be null in this case the user does not want a devmode, this simplifies the caller. This routine also takes care of a few special cases. If pDevMode is -1 it returns the printers default devmode. If the printers default devmode is null this function will succeed. Arguments: pIniPrinter - pointer to ini printer structure for the specified printer pDevMode, - pointer to devmode to allocation, this is optional *ppDevMode - pointer where to return new allocated devmode Return Value: TRUE success, FALSE an error occurred. Last Error: ERROR_INVALID_PARAMETER if any of the required parameters are invalid. --*/ BOOL AllocResetDevMode( IN PINIPRINTER pIniPrinter, IN DWORD TypeofHandle, IN PDEVMODE pDevMode, OPTIONAL OUT PDEVMODE *ppDevMode ) { BOOL bRetval = FALSE; // // Validate the input parameters. // if (pIniPrinter && ppDevMode) { // // Initalize the out parameter // *ppDevMode = NULL; // // If pDevMode == -1 then we want to return the printers default devmode. // The -1 token is for internal use, and currently only used by the server // service, which does not run in the context of the user, hence we must // not use a per user devmode. // if (pDevMode == (PDEVMODE)-1) { // // If the handle is a 3.x we must convert the devmode. // if (TypeofHandle & PRINTER_HANDLE_3XCLIENT) { *ppDevMode = ConvertDevModeToSpecifiedVersion(pIniPrinter, pDevMode, NULL, NULL, NT3X_VERSION); bRetval = !!*ppDevMode; } else { // // Get the printer's default devmode. // pDevMode = pIniPrinter->pDevMode; } } // // At this point the pDevMode may be either the passed in devmode // or the default devmode on the printer. If the devmode on the printer // is null then this function will succeed but will not return a devmode. // if (pDevMode && pDevMode != (PDEVMODE)-1) { // // Make a copy of the passed in devmode, this is less efficient // however it simplfies the callers clean up code, less chance for // a mistake. // UINT cbSize = pDevMode->dmSize + pDevMode->dmDriverExtra; *ppDevMode = AllocSplMem(cbSize); bRetval = !!*ppDevMode; if (bRetval) { // // This is OK, YResetPrinter validates that the devmode is of the // correct size. // memcpy(*ppDevMode, pDevMode, cbSize); } } else { DBGMSG(DBG_TRACE,("LocalResetPrinter: Not resetting the pDevMode field\n")); bRetval = TRUE; } } else { // // The function returns a bool, we must set the last error. // SetLastError(ERROR_INVALID_PARAMETER); } return bRetval; } /*++ Routine Name: AllocResetDataType Routine Description: This routine allocates a new datatype that will be associated with a printer handle in ResetPrinter. Arguments: pIniPrinter - pointer to ini printer structure for the specified printer pDatatype - the new data type to validate and allocate ppDatatype - where to return the new datatype ppIniPrintProc - pointer where to return the associated print processor Return Value: TRUE function succeeded, FALSE an error occurred, use GetLastError() for extended error information. Last Error: ERROR_INVALID_PARAMETER if a required parameter is invalid. ERROR_INVALID_DATATYPE if the specified datatype is invalid. --*/ BOOL AllocResetDataType( IN PINIPRINTER pIniPrinter, IN PCWSTR pDatatype, OUT PCWSTR *ppDatatype, OUT PINIPRINTPROC *ppIniPrintProc ) { BOOL bRetval = FALSE; // // Validate the input parameters. // if (pIniPrinter && ppDatatype && ppIniPrintProc) { // // Initalize the out parameters // *ppDatatype = NULL; *ppIniPrintProc = NULL; if (pDatatype) { // // If the datatype is -1 we are being requested to // return the default datatype for this printer. // if (pDatatype == (LPWSTR)-1 && pIniPrinter->pDatatype) { *ppIniPrintProc = FindDatatype(pIniPrinter->pIniPrintProc, pIniPrinter->pDatatype); } else { *ppIniPrintProc = FindDatatype(pIniPrinter->pIniPrintProc, (PWSTR)pDatatype); } // // If the print process was found, the datatype is valid, // allocate the new datatype. // if (*ppIniPrintProc) { *ppDatatype = AllocSplStr(pIniPrinter->pDatatype); bRetval = !!*ppDatatype; } else { SetLastError(ERROR_INVALID_DATATYPE); } } else { DBGMSG(DBG_TRACE,("LocalResetPrinter: Not resetting the pDatatype field\n")); bRetval = TRUE; } } else { // // The function returns a bool, we must set the last error. // SetLastError(ERROR_INVALID_PARAMETER); } return bRetval; } /*++ Routine Name: SplResetPrinter Routine Description: The ResetPrinter function lets an application specify the data type and device mode values that are used for printing documents submitted by the StartDocPrinter function. These values can be overridden by using the SetJob function once document printing has started. This routine basically has two jobs. To reset the devmode of the printer handle and to reset the datatype of the printer handle. Each or both operation could be ignored, if the devmode null the devmode will not change if the datatye is null the datatype should not change. If both operations are requested and any one fails then both operations should fail. Arguments: hPrinter - Valid printer handle pDefault - Pointer to a printer defaults structure that has a devmode and data type. Return Value: TRUE function succeeded, FALSE an error occurred, use GetLastError() for extended error information. Last Error: ERROR_INVALID_PARAMETER if the pDefault is NULL, ERROR_INVALID_DATATYPE if the new datatype specified is unknown or invalid --*/ BOOL SplResetPrinter( IN HANDLE hPrinter, IN LPPRINTER_DEFAULTS pDefaults ) { PSPOOL pSpool = (PSPOOL)hPrinter; BOOL bRetval = FALSE; PINIPRINTPROC pNewPrintProc = NULL; LPWSTR pNewDatatype = NULL; PDEVMODE pNewDevMode = NULL; DBGMSG(DBG_TRACE, ("ResetPrinter( %08x )\n", hPrinter)); // // Validate the printer handle. // if (ValidateSpoolHandle(pSpool, PRINTER_HANDLE_SERVER)) { // // Validate the pDefaults // if (pDefaults) { // // Enter the spooler semaphore. // EnterSplSem(); // // Get the new devmode, a null input devmode indicatest the caller // was not interesting in changing the devmode. // bRetval = AllocResetDevMode(pSpool->pIniPrinter, pSpool->TypeofHandle, pDefaults->pDevMode, &pNewDevMode); if (bRetval) { // // Get the new datatype and printprocessor, a null input datatype // indicates the caller was not interested in changing the datatype. // bRetval = AllocResetDataType(pSpool->pIniPrinter, pDefaults->pDatatype, &pNewDatatype, &pNewPrintProc); } if (bRetval) { // // Release the previous devmode provided we have a new devmode to set // a new devmode may not be available in the case the caller did not // request a devmode change. // if (pNewDevMode) { FreeSplMem(pSpool->pDevMode); pSpool->pDevMode = pNewDevMode; pNewDevMode = NULL; } // // Release the previous datatype provided we have a new datatype to set // a new datatype may not be available in the case the caller did not // request a devmode change. // if (pNewDatatype && pNewPrintProc) { FreeSplStr(pSpool->pDatatype); pSpool->pDatatype = pNewDatatype; pNewDatatype = NULL; // // Release the previous print processor, and assign the new one. // pSpool->pIniPrintProc->cRef--; pSpool->pIniPrintProc = pNewPrintProc; pSpool->pIniPrintProc->cRef++; pNewPrintProc = NULL; } } // // Exit the spooler semaphore. // LeaveSplSem(); // // Always release any resources, the mem free routines will handle null pointers. // FreeSplMem(pNewDevMode); FreeSplMem(pNewDatatype); } else { SetLastError(ERROR_INVALID_PARAMETER); } } return bRetval; } BOOL CopyPrinterIni( PINIPRINTER pIniPrinter, LPWSTR pNewName ) { HKEY hPrinterKey=NULL; DWORD Status; PWSTR pSourceKeyName = NULL; PWSTR pDestKeyName = NULL; HANDLE hToken; PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler; BOOL bReturnValue = TRUE; SPLASSERT( pIniSpooler != NULL); SPLASSERT( pIniSpooler->signature == ISP_SIGNATURE ); hToken = RevertToPrinterSelf(); if (!hToken) { bReturnValue = FALSE; goto error; } if (!(pSourceKeyName = SubChar(pIniPrinter->pName, L'\\', L','))) { bReturnValue = FALSE; goto error; } if (!(pDestKeyName = SubChar(pNewName, L'\\', L','))) { bReturnValue = FALSE; goto error; } if( !CopyRegistryKeys( pIniSpooler->hckPrinters, pSourceKeyName, pIniSpooler->hckPrinters, pDestKeyName, pIniSpooler )) { bReturnValue = FALSE; goto error; } error: FreeSplStr(pSourceKeyName); FreeSplStr(pDestKeyName); if (hToken) { if (!ImpersonatePrinterClient(hToken) && bReturnValue) { bReturnValue = FALSE; } } return bReturnValue; } VOID FixDevModeDeviceName( LPWSTR pPrinterName, PDEVMODE pDevMode, DWORD cbDevMode) /*++ Routine Description: Fixes up the dmDeviceName field of the DevMode to be the same as the printer name. Arguments: pPrinterName - Name of the printer (qualified with server for remote) pDevMode - DevMode to fix up cbDevMode - byte count of devmode. Return Value: --*/ { DWORD cbDeviceMax; DWORD cchDeviceStrLenMax; // // Compute the maximum length of the device name string // this is the min of the structure and allocated space. // SPLASSERT(cbDevMode && pDevMode); if(cbDevMode && pDevMode) { cbDeviceMax = ( cbDevMode < sizeof(pDevMode->dmDeviceName)) ? cbDevMode : sizeof(pDevMode->dmDeviceName); SPLASSERT(cbDeviceMax); cchDeviceStrLenMax = (cbDeviceMax / sizeof(pDevMode->dmDeviceName[0])); StringCchCopy(pDevMode->dmDeviceName, cchDeviceStrLenMax, pPrinterName); } } BOOL CopyPrinterDevModeToIniPrinter( PINIPRINTER pIniPrinter, PDEVMODE pDevMode) { BOOL bReturn = TRUE; DWORD dwInSize = 0; DWORD dwCurSize = 0; PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler; WCHAR PrinterName[ MAX_UNC_PRINTER_NAME ]; if (pDevMode) { dwInSize = pDevMode->dmSize + pDevMode->dmDriverExtra; if (pIniPrinter->pDevMode) { // // Detect if the devmodes are identical // if they are, no need to copy or send devmode. // (Skip the device name though!) // dwCurSize = pIniPrinter->pDevMode->dmSize + pIniPrinter->pDevMode->dmDriverExtra; if (dwInSize == dwCurSize) { if (dwInSize > sizeof(pDevMode->dmDeviceName)) { if (!memcmp(&pDevMode->dmSpecVersion, &pIniPrinter->pDevMode->dmSpecVersion, dwCurSize - sizeof(pDevMode->dmDeviceName))) { // // No need to copy this devmode because its identical // to what we already have. // DBGMSG(DBG_TRACE,("Identical DevModes, no update\n")); bReturn = FALSE; goto FixupName; } } } // // Free the devmode which we already have. // FreeSplMem(pIniPrinter->pDevMode); } pIniPrinter->cbDevMode = pDevMode->dmSize + pDevMode->dmDriverExtra; SPLASSERT(pIniPrinter->cbDevMode); if (pIniPrinter->pDevMode = AllocSplMem(pIniPrinter->cbDevMode)) { // // OK. This assumes the pIniPrinter's devmode has already been validated. // memcpy(pIniPrinter->pDevMode, pDevMode, pIniPrinter->cbDevMode); // // Prepend the machine name if this is not localspl // if ( pIniSpooler != pLocalIniSpooler ) { // For Non Local Printers prepend the Machine Name StringCchPrintf(PrinterName, COUNTOF(PrinterName), L"%ws\\%ws", pIniSpooler->pMachineName, pIniPrinter->pName); } else { StringCchPrintf(PrinterName, COUNTOF(PrinterName), L"%ws", pIniPrinter->pName); } BroadcastChange(pIniSpooler, WM_DEVMODECHANGE, 0, (LPARAM)PrinterName); } } else { // // No old, no new, so no change. // if (!pIniPrinter->pDevMode) return FALSE; } FixupName: if (pIniPrinter->pDevMode) { // // Fix up the DEVMODE.dmDeviceName field. // FixDevModeDeviceName(pIniPrinter->pName, pIniPrinter->pDevMode, pIniPrinter->cbDevMode); } return bReturn; } BOOL NameAndSecurityCheck( LPCWSTR pServer ) { PINISPOOLER pIniSpooler; BOOL bReturn = TRUE; pIniSpooler = FindSpoolerByNameIncRef( (LPWSTR)pServer, NULL ); if( !pIniSpooler ){ return ROUTER_UNKNOWN; } // Check if the call is for the local machine. if ( pServer && *pServer ) { if ( !MyName((LPWSTR) pServer, pIniSpooler )) { bReturn = FALSE; goto CleanUp; } } // Check for admin priviledges. if ( !ValidateObjectAccess( SPOOLER_OBJECT_SERVER, SERVER_ACCESS_ADMINISTER, NULL, NULL, pIniSpooler )) { bReturn = FALSE; } CleanUp: // The Local case is handled in the router. FindSpoolerByNameDecRef( pIniSpooler ); return bReturn; } BOOL LocalAddPerMachineConnection( LPCWSTR pServer, LPCWSTR pPrinterName, LPCWSTR pPrintServer, LPCWSTR pProvider ) { return NameAndSecurityCheck(pServer); } BOOL LocalDeletePerMachineConnection( LPCWSTR pServer, LPCWSTR pPrinterName ) { return NameAndSecurityCheck(pServer); } BOOL LocalEnumPerMachineConnections( LPCWSTR pServer, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned ) { SetLastError(ERROR_INVALID_NAME); return FALSE; } BOOL UpdatePrinterNetworkName( PINIPRINTER pIniPrinter, LPWSTR pszPorts ) { PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler; DWORD dwLastError = ERROR_SUCCESS; LPWSTR pKeyName = NULL; HANDLE hToken; BOOL bReturnValue; HANDLE hPrinterKey = NULL; HANDLE hPrinterHttpDataKey = NULL; SplInSem(); hToken = RevertToPrinterSelf(); if ( hToken == FALSE ) { DBGMSG( DBG_TRACE, ("UpdatePrinterIni failed RevertToPrinterSelf %x\n", GetLastError() )); } if (!(pKeyName = SubChar(pIniPrinter->pName, L'\\', L','))) { dwLastError = GetLastError(); goto Cleanup; } if ( !PrinterCreateKey( pIniSpooler->hckPrinters, pKeyName, &hPrinterKey, &dwLastError, pIniSpooler )) { goto Cleanup; } if ( !PrinterCreateKey( hPrinterKey, L"HttpData", &hPrinterHttpDataKey, &dwLastError, pIniSpooler )) { goto Cleanup; } RegSetString( hPrinterHttpDataKey, L"UIRealNetworkName", pszPorts, &dwLastError, pIniSpooler ); Cleanup: FreeSplStr(pKeyName); if ( hPrinterHttpDataKey ) SplRegCloseKey( hPrinterHttpDataKey, pIniSpooler); if ( hPrinterKey ) SplRegCloseKey( hPrinterKey, pIniSpooler); if ( hToken ) ImpersonatePrinterClient( hToken ); if ( dwLastError != ERROR_SUCCESS ) { SetLastError( dwLastError ); bReturnValue = FALSE; } else { bReturnValue = TRUE; } return bReturnValue; } DWORD KMPrintersAreBlocked( ) { return GetSpoolerNumericPolicy(szKMPrintersAreBlocked, DefaultKMPrintersAreBlocked); } PINIPRINTER FindPrinterAnywhere( LPTSTR pName, DWORD SpoolerType ) /*++ Routine Description: Search for a printer name in all pIniSpoolers. Arguments: pName - Name of printer to search for. SpoolerType - Type of spooler the printer should reside in. Return Value: PINIPRINTER - Found pIniPrinter NULL - Not found. --*/ { LPCTSTR pszLocalName; PINIPRINTER pIniPrinter; PINISPOOLER pIniSpooler = FindSpoolerByName( pName, &pszLocalName ); SplInSem(); if( pIniSpooler && (( pIniPrinter = FindPrinter( pszLocalName, pIniSpooler )) || ( pIniPrinter = FindPrinterShare( pszLocalName, pIniSpooler )))){ if( pIniPrinter->pIniSpooler->SpoolerFlags & SpoolerType ){ return pIniPrinter; } } return NULL; } BOOL LocalAddPrinterConnection( LPWSTR pName ) { // // Allow us to make clustered connections to local printers // (they appear to be remote). // BOOL bReturn = FALSE; EnterSplSem(); if( FindPrinterAnywhere( pName, SPL_TYPE_CLUSTER )){ bReturn = TRUE; } LeaveSplSem(); SetLastError(ERROR_INVALID_NAME); return bReturn; } BOOL LocalDeletePrinterConnection( LPWSTR pName ) { // // Allow us to remove clustered connections to local printers // (they appear to be remote). // BOOL bReturn = FALSE; EnterSplSem(); if( FindPrinterAnywhere( pName, SPL_TYPE_CLUSTER )){ bReturn = TRUE; } LeaveSplSem(); SetLastError(ERROR_INVALID_NAME); return bReturn; }