/*++ Copyright (c) 1996 Microsoft Corporation Module Name: faxmon.c Abstract: Implementation of the following print monitor entry points: InitializePrintMonitor OpenPort ClosePort StartDocPort EndDocPort WritePort ReadPort Environment: Windows NT fax print monitor Revision History: 05/09/96 -davidx- Remove caching of ports from the monitor. 02/22/96 -davidx- Created it. mm/dd/yy -author- description --*/ #include "faxmon.h" #include "tiff.h" #include "faxreg.h" #include // // Determine whether we're at the beginning of a TIFF file // #define ValidTiffFileHeader(p) \ (((LPSTR) (p))[0] == 'I' && ((LPSTR) (p))[1] == 'I' && \ ((PBYTE) (p))[2] == 42 && ((PBYTE) (p))[3] == 0) // // Read a DWORD value from an unaligned address // #define ReadUnalignedDWord(p) *((DWORD UNALIGNED *) (p)) // // Write a DWORD value to an unaligned address // #define WriteUnalignedDWord(p, value) (*((DWORD UNALIGNED *) (p)) = (value)) // // Fax monitor name string // TCHAR faxMonitorName[CCHDEVICENAME] = TEXT("Windows NT Fax Monitor"); // // DLL instance handle // HANDLE ghInstance = NULL; // // Retry parameters when failing to connect to the fax service // default = infinite retry with 5 seconds interval // DWORD connectRetryCount = 0; DWORD connectRetryInterval = 5; BOOL DllEntryPoint( HANDLE hModule, ULONG ulReason, PCONTEXT pContext ) /*++ Routine Description: DLL initialization procedure. Arguments: hModule - DLL instance handle ulReason - Reason for the call pContext - Pointer to context (not used by us) Return Value: TRUE if DLL is initialized successfully, FALSE otherwise. --*/ { switch (ulReason) { case DLL_PROCESS_ATTACH: ghInstance = hModule; LoadString(ghInstance, IDS_FAX_MONITOR_NAME, faxMonitorName, CCHDEVICENAME); break; case DLL_PROCESS_DETACH: break; } return TRUE; } LPMONITOREX InitializePrintMonitor( LPTSTR pRegistryRoot ) /*++ Routine Description: Initialize the print monitor Arguments: pRegistryRoot = Points to a string that specifies the registry root for the monitor Return Value: Pointer to a MONITOREX structure which contains function pointers to other print monitor entry points. NULL if there is an error. --*/ { static MONITOREX faxmonFuncs = { sizeof(MONITOR), { FaxMonEnumPorts, FaxMonOpenPort, NULL, // OpenPortEx FaxMonStartDocPort, FaxMonWritePort, FaxMonReadPort, FaxMonEndDocPort, FaxMonClosePort, FaxMonAddPort, FaxMonAddPortEx, FaxMonConfigurePort, FaxMonDeletePort, NULL, // GetPrinterDataFromPort NULL, // SetPortTimeOuts } }; Trace("InitializePrintMonitor"); // // Get fax service connection retry parameters from the registry // if (pRegistryRoot) { HKEY hRegKey; LONG status; status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, pRegistryRoot, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &hRegKey, NULL); if (status == ERROR_SUCCESS) { connectRetryCount = GetRegistryDWord(hRegKey, REGVAL_CONNECT_RETRY_COUNT, connectRetryCount); connectRetryInterval = GetRegistryDWord(hRegKey, REGVAL_CONNECT_RETRY_INTERVAL, connectRetryInterval); RegCloseKey(hRegKey); } } return &faxmonFuncs; } BOOL FaxMonOpenPort( LPTSTR pPortName, PHANDLE pHandle ) /*++ Routine Description: Provides a port for a newly connected printer Arguments: pName - Points to a string that specifies the port name pHandle - Returns a handle to the port Return Value: TRUE if successful, FALSE if there is an error --*/ { PFAXPORT pFaxPort = NULL; Trace("OpenPort"); Assert(pHandle != NULL && pPortName != NULL); // // Get information about the specified port // if ((pFaxPort = MemAllocZ(sizeof(FAXPORT))) && (pPortName = DuplicateString(FAX_PORT_NAME))) { pFaxPort->startSig = pFaxPort->endSig = pFaxPort; pFaxPort->pName = pPortName; pFaxPort->hFile = INVALID_HANDLE_VALUE; } else { MemFree(pFaxPort); pFaxPort = NULL; } *pHandle = (HANDLE) pFaxPort; return (*pHandle != NULL); } VOID FreeFaxJobInfo( PFAXPORT pFaxPort ) /*++ Routine Description: Free up memory used for maintaining information about the current job Arguments: pFaxPort - Points to a fax port structure Return Value: NONE --*/ { // // Close and delete the temporary file if necessary // if (pFaxPort->hFile != INVALID_HANDLE_VALUE) { CloseHandle(pFaxPort->hFile); pFaxPort->hFile = INVALID_HANDLE_VALUE; } if (pFaxPort->pFilename) { DeleteFile(pFaxPort->pFilename); MemFree(pFaxPort->pFilename); pFaxPort->pFilename = NULL; } if (pFaxPort->hPrinter) { ClosePrinter(pFaxPort->hPrinter); pFaxPort->hPrinter = NULL; } MemFree(pFaxPort->pPrinterName); pFaxPort->pPrinterName = NULL; MemFree(pFaxPort->pParameters); pFaxPort->pParameters = NULL; ZeroMemory(&pFaxPort->jobParam, sizeof(pFaxPort->jobParam)); // // Disconnect from the fax service if necessary // if (pFaxPort->hFaxSvc) { if (! pFaxPort->pFaxClose(pFaxPort->hFaxSvc)) { Error(("FaxClose failed: %d\n", GetLastError())); } FreeLibrary( pFaxPort->hWinFax ); pFaxPort->hFaxSvc = NULL; pFaxPort->pFaxConnectFaxServerW = NULL; pFaxPort->pFaxClose = NULL; pFaxPort->pFaxSendDocumentW = NULL; pFaxPort->pFaxAccessCheck = NULL; } } BOOL FaxMonClosePort( HANDLE hPort ) /*++ Routine Description: Closes the port specified by hPort when there are no printers connected to it Arguments: hPort - Specifies the handle of the port to be close Return Value: TRUE if successful, FALSE if there is an error --*/ { PFAXPORT pFaxPort = (PFAXPORT) hPort; Trace("ClosePort"); // // Make sure we have a valid handle // if (! ValidFaxPort(pFaxPort)) { Error(("Trying to close an invalid fax port handle\n")); SetLastError(ERROR_INVALID_HANDLE); return FALSE; } // // Free up memory used for maintaining information about the current job // FreeFaxJobInfo(pFaxPort); MemFree(pFaxPort->pName); MemFree(pFaxPort); return TRUE; } LPTSTR CreateTempFaxFile( VOID ) /*++ Routine Description: Create a temporary file in the system spool directory for storing fax data Arguments: NONE Return Value: Pointer to the name of the newly created temporary file NULL if there is an error --*/ { //TCHAR spoolDir[MAX_PATH]; //HANDLE hServer; LPTSTR pFilename; TCHAR TempDir[MAX_PATH]; TCHAR FileName[MAX_PATH]; // // Allocate a memory buffer for holding the temporary filename // if (pFilename = MemAlloc(sizeof(TCHAR) * MAX_PATH)) { if (!GetTempPath(sizeof(TempDir)/sizeof(TCHAR),TempDir)|| !GetTempFileName(TempDir, TEXT("fax"), 0, FileName)) { MemFree(pFilename); pFilename = NULL; } else { lstrcpy(pFilename,FileName); } } if (! pFilename) Error(("Failed to create temporary file in the spool directory\n")); return pFilename; } BOOL OpenTempFaxFile( PFAXPORT pFaxPort, BOOL doAppend ) /*++ Routine Description: Open a handle to the current fax job file associated with a port Arguments: pFaxPort - Points to a fax port structure doAppend - Specifies whether to discard existing data in the file or append new data to it Return Value: TRUE if successful, FALSE otherwise --*/ { DWORD creationFlags; Assert(pFaxPort->pFilename && pFaxPort->hFile == INVALID_HANDLE_VALUE); Verbose(("Temporary fax job file: %ws\n", pFaxPort->pFilename)); // // Open the file for reading and writing // creationFlags = doAppend ? OPEN_ALWAYS : (OPEN_ALWAYS | TRUNCATE_EXISTING); pFaxPort->hFile = CreateFile(pFaxPort->pFilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, creationFlags, FILE_ATTRIBUTE_NORMAL, NULL); // // If we're appending, then move the file pointer to end of file // if (doAppend && pFaxPort->hFile != INVALID_HANDLE_VALUE && SetFilePointer(pFaxPort->hFile, 0, NULL, FILE_END) == 0xffffffff) { Error(("SetFilePointer failed: %d\n", GetLastError())); CloseHandle(pFaxPort->hFile); pFaxPort->hFile = INVALID_HANDLE_VALUE; } return (pFaxPort->hFile != INVALID_HANDLE_VALUE); } LPCTSTR ExtractFaxTag( LPCTSTR pTagKeyword, LPCTSTR pTaggedStr, INT *pcch ) /*++ Routine Description: Find the value of for the specified tag in a tagged string. Arguments: pTagKeyword - specifies the interested tag keyword pTaggedStr - points to the tagged string to be searched pcch - returns the length of the specified tag value (if found) Return Value: Points to the value for the specified tag. NULL if the specified tag is not found NOTE: Tagged strings have the following form: valuevalue The format of tags is defined as: <$FAXTAG$ tag-name> There is exactly one space between the tag keyword and the tag name. Characters in a tag are case-sensitive. --*/ { LPCTSTR pValue; if (pValue = _tcsstr(pTaggedStr, pTagKeyword)) { pValue += _tcslen(pTagKeyword); if (pTaggedStr = _tcsstr(pValue, FAXTAG_PREFIX)) *pcch = (INT)(pTaggedStr - pValue); else *pcch = _tcslen(pValue); } return pValue; } BOOL GetJobInfo( PFAXPORT pFaxPort, DWORD jobId ) /*++ Routine Description: Retrieve recipient information from the devmode associated with the job Arguments: pFaxPort - Points to a fax port structure jobId - Specifies the current job ID Return Value: TRUE if the job parameters are successfully retrieved or the fax job is from a downlevel fax client. FALSE if there is an error. --*/ { JOB_INFO_2 *pJobInfo2; LPCTSTR pParameters = NULL; // // Retrieve the parameter string associated with the specified job. // If there is no job parameter or the parameter contains the tag // <$FAXTAG$ DOWNLEVEL>, then we assume the job comes from downlevel client. // ZeroMemory(&pFaxPort->jobParam, sizeof(pFaxPort->jobParam)); pFaxPort->jobParam.SizeOfStruct = sizeof( FAX_JOB_PARAM ); if ((pJobInfo2 = MyGetJob(pFaxPort->hPrinter, 2, jobId)) == NULL || (pParameters = pJobInfo2->pParameters) == NULL || _tcsstr(pParameters, FAXTAG_DOWNLEVEL_CLIENT) != NULL) { MemFree(pJobInfo2); return TRUE; } if ((pFaxPort->pParameters = DuplicateString(pParameters)) != NULL) { // // Tags used to pass information about fax jobs // static LPCTSTR faxtagNames[NUM_JOBPARAM_TAGS] = { FAXTAG_RECIPIENT_NUMBER, FAXTAG_RECIPIENT_NAME, FAXTAG_TSID, FAXTAG_SENDER_NAME, FAXTAG_SENDER_COMPANY, FAXTAG_SENDER_DEPT, FAXTAG_BILLING_CODE, FAXTAG_WHEN_TO_SEND, FAXTAG_SEND_AT_TIME, FAXTAG_PROFILE_NAME, FAXTAG_EMAIL_NAME }; LPTSTR WhenToSend = NULL; LPTSTR SendAtTime = NULL; LPTSTR DeliveryReportType = NULL; LPTSTR *fieldStr[NUM_JOBPARAM_TAGS] = { (LPTSTR *)&pFaxPort->jobParam.RecipientNumber, (LPTSTR *)&pFaxPort->jobParam.RecipientName, (LPTSTR *)&pFaxPort->jobParam.Tsid, (LPTSTR *)&pFaxPort->jobParam.SenderName, (LPTSTR *)&pFaxPort->jobParam.SenderCompany, (LPTSTR *)&pFaxPort->jobParam.SenderDept, (LPTSTR *)&pFaxPort->jobParam.BillingCode, &WhenToSend, &SendAtTime, (LPTSTR *)&pFaxPort->jobParam.DeliveryReportAddress, &DeliveryReportType }; INT fieldLen[NUM_JOBPARAM_TAGS]; INT count; pParameters = pFaxPort->pParameters; Verbose(("JOB_INFO_2.pParameter = %ws\n", pParameters)); // // Extract individual fields out of the tagged string // for (count=0; count < NUM_JOBPARAM_TAGS; count++) { *fieldStr[count] = (LPTSTR)ExtractFaxTag(faxtagNames[count], pParameters, &fieldLen[count]); } // // Null-terminate each field // for (count=0; count < NUM_JOBPARAM_TAGS; count++) { if (*fieldStr[count]) { (*fieldStr[count])[fieldLen[count]] = NUL; } } if (WhenToSend) { if (_tcsicmp( WhenToSend, TEXT("cheap") ) == 0) { pFaxPort->jobParam.ScheduleAction = JSA_DISCOUNT_PERIOD; } else if (_tcsicmp( WhenToSend, TEXT("at") ) == 0) { pFaxPort->jobParam.ScheduleAction = JSA_SPECIFIC_TIME; } } if (SendAtTime) { if (_tcslen(SendAtTime) == 5 && SendAtTime[2] == L':' && _istdigit(SendAtTime[0]) && _istdigit(SendAtTime[1]) && _istdigit(SendAtTime[3]) && _istdigit(SendAtTime[4])) { DWORDLONG FileTime; SYSTEMTIME LocalTime; INT Minutes; INT SendMinutes; SendAtTime[2] = 0; // // Calculate the number of minutes from now to send and add that to the current time. // GetLocalTime( &LocalTime ); SystemTimeToFileTime( &LocalTime, (LPFILETIME) &FileTime ); SendMinutes = min(23,_ttoi( &SendAtTime[0] )) * 60 + min(59,_ttoi( &SendAtTime[3] )); Minutes = LocalTime.wHour * 60 + LocalTime.wMinute; Minutes = SendMinutes - Minutes; // Account for passing midnight // if (Minutes < 0) { Minutes += 24 * 60; } FileTime += (DWORDLONG)(Minutes * 60I64 * 1000I64 * 1000I64 * 10I64); FileTimeToSystemTime((LPFILETIME) &FileTime, &pFaxPort->jobParam.ScheduleTime ); SendAtTime[2] = L':'; } } if (DeliveryReportType) { if (_tcsicmp( DeliveryReportType, TEXT("email") ) == 0) { pFaxPort->jobParam.DeliveryReportType = DRT_EMAIL; } else if (_tcsicmp( DeliveryReportType, TEXT("inbox") ) == 0) { pFaxPort->jobParam.DeliveryReportType = DRT_INBOX; } } if (pFaxPort->jobParam.RecipientNumber == NULL) { Error(("Missing recipient phone number.\n")); SetJob(pFaxPort->hPrinter, jobId, 0, NULL, JOB_CONTROL_PAUSE); SetLastError(ERROR_INVALID_PARAMETER); } } MemFree(pJobInfo2); return (pFaxPort->jobParam.RecipientNumber != NULL); } BOOL FaxMonStartDocPort( HANDLE hPort, LPTSTR pPrinterName, DWORD JobId, DWORD Level, LPBYTE pDocInfo ) /*++ Routine Description: Spooler calls this function to start a new print job on the port Arguments: hPort - Identifies the port pPrinterName - Specifies the name of the printer to which the job is being sent JobId - Identifies the job being sent by the spooler Level - Specifies the DOC_INFO_x level pDocInfo - Points to the document information Return Value: TRUE if successful, FALSE if there is an error --*/ { PFAXPORT pFaxPort = (PFAXPORT) hPort; Verbose(("Entering StartDocPort: %d ...\n", JobId)); // // Make sure we have a valid handle // if (! ValidFaxPort(pFaxPort)) { Error(("StartDocPort is given an invalid fax port handle\n")); SetLastError(ERROR_INVALID_HANDLE); return FALSE; } // // Check if we're at the beginning of a series of chained jobs // if (! pFaxPort->hFaxSvc) { PJOB_INFO_1 pJobInfo1; PORT_INFO_3 portInfo3; HANDLE hPrinter = NULL; BOOL offline = FALSE; DWORD count = connectRetryCount; DWORD jobStatus = 0; Assert(pFaxPort->pPrinterName == NULL && pFaxPort->hPrinter == NULL && pFaxPort->pParameters == NULL && pFaxPort->pFilename == NULL && pFaxPort->hFile == INVALID_HANDLE_VALUE); // // load the winfax dll // pFaxPort->hWinFax = LoadLibrary( L"winfax.dll" ); if (pFaxPort->hWinFax == NULL) { Error(("LoadLibrary failed loading winfax.dll\n")); return FALSE; } // // get the function addresses // pFaxPort->pFaxConnectFaxServerW = (PFAXCONNECTFAXSERVERW) GetProcAddress( pFaxPort->hWinFax, "FaxConnectFaxServerW" ); pFaxPort->pFaxClose = (PFAXCLOSE) GetProcAddress( pFaxPort->hWinFax, "FaxClose" ); pFaxPort->pFaxSendDocumentW = (PFAXSENDDOCUMENTW) GetProcAddress( pFaxPort->hWinFax, "FaxSendDocumentW" ); pFaxPort->pFaxAccessCheck = (PFAXACCESSCHECK) GetProcAddress( pFaxPort->hWinFax, "FaxAccessCheck" ); if (pFaxPort->pFaxConnectFaxServerW == NULL || pFaxPort->pFaxClose == NULL || pFaxPort->pFaxSendDocumentW == NULL || pFaxPort->pFaxAccessCheck == NULL) { Error(("GetProcAddress failed loading winfax.dll\n")); return FALSE; } // // Connect to the fax service and obtain a session handle // while (! pFaxPort->pFaxConnectFaxServerW(NULL, &pFaxPort->hFaxSvc)) { Error(("FaxConnectFaxServer failed: %d\n", GetLastError())); pFaxPort->hFaxSvc = NULL; if (! offline) { portInfo3.dwStatus = PORT_STATUS_OFFLINE; portInfo3.pszStatus = NULL; portInfo3.dwSeverity = PORT_STATUS_TYPE_WARNING; if (! SetPort(NULL, pFaxPort->pName, 3, (LPBYTE) &portInfo3)) Error(("SetPort failed: %d\n", GetLastError())); } offline = TRUE; Sleep(connectRetryInterval * 1000); // // Check if the job has been deleted or restarted // if (!hPrinter && !OpenPrinter(pPrinterName, &hPrinter, NULL)) { Error(("OpenPrinter failed: %d\n", GetLastError())); hPrinter = NULL; } else if (pJobInfo1 = MyGetJob(hPrinter, 1, JobId)) { jobStatus = pJobInfo1->Status; } MemFree(pJobInfo1); if (--count == 0 || (jobStatus & (JOB_STATUS_DELETING| JOB_STATUS_DELETED| JOB_STATUS_RESTART))) { break; } } // // Remove the offline status on the port // if (offline) { portInfo3.dwStatus = 0; portInfo3.pszStatus = NULL; portInfo3.dwSeverity = PORT_STATUS_TYPE_INFO; if (! SetPort(NULL, pFaxPort->pName, 3, (LPBYTE) &portInfo3)) { Error(("SetPort failed: %d\n", GetLastError())); } } if (hPrinter) { ClosePrinter(hPrinter); } if (pFaxPort->hFaxSvc) { if (!pFaxPort->pFaxAccessCheck(pFaxPort->hFaxSvc, FAX_JOB_SUBMIT) ) { FreeFaxJobInfo(pFaxPort); Error(("FaxAccessCheck failed : %d\n", GetLastError() )); SetLastError(ERROR_ACCESS_DENIED); return FALSE; } // HANDLE hToken; // // The monitor runs in the context of the current job's owner. // In order to create temporary files in the spool directory, // we need to revert to the spooler context first. // /* if (! (hToken = RevertToPrinterSelf())) Error(("RevertToPrinterSelf failed: %d\n", GetLastError())); */ // // Remember the printer name because we'll need it at EndDocPort time. // Get a temporary filename and open it for reading and writing. // Remember other job related information // if (! (pFaxPort->pPrinterName = DuplicateString(pPrinterName)) || ! OpenPrinter(pPrinterName, &pFaxPort->hPrinter, NULL) || ! GetJobInfo(pFaxPort, JobId) || ! (pFaxPort->pFilename = CreateTempFaxFile()) || ! OpenTempFaxFile(pFaxPort, FALSE)) { FreeFaxJobInfo(pFaxPort); } else pFaxPort->jobId = pFaxPort->nextJobId = JobId; // // Switch back to the original context if necessary // /* if (hToken && !ImpersonatePrinterClient(hToken)) Error(("ImpersonatePrinterClient failed: %d\n", GetLastError())); */ } } else { Assert(pFaxPort->jobId == JobId); } return (pFaxPort->hFaxSvc != NULL); } INT FixUpFaxFile( PFAXPORT pFaxPort ) /*++ Routine Description: Fixed up the saved print job data into a well-formed TIFF file Arguments: pFaxPort - Points to a fax port structure Return Value: error code FAXERR_* --*/ { DWORD fileSize; PBYTE pFileEnd, pFileHdr; PBYTE pFileView = NULL; HANDLE hFileMap = NULL; INT result = FAXERR_BAD_TIFF; // // Get the size of print job file // FlushFileBuffers(pFaxPort->hFile); if ((fileSize = GetFileSize(pFaxPort->hFile, NULL)) == 0) return FAXERR_IGNORE; if (fileSize == 0xffffffff) return FAXERR_FATAL; __try { // // Map the fax job file into memory // if ((hFileMap = CreateFileMapping(pFaxPort->hFile, NULL, PAGE_READWRITE, 0, 0, NULL)) && (pFileHdr = pFileView = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, fileSize)) && ValidTiffFileHeader(pFileHdr)) { DWORD ifdOffset, maxOffset, fileOffset; PBYTE pIfdOffset; // // A fax print job may contain multiple TIFF files. Each each iteration // of the outer loop below deals with a single embedded TIFF file. // pFileEnd = pFileHdr + fileSize; ifdOffset = ReadUnalignedDWord(pFileHdr + sizeof(DWORD)); do { Verbose(("Reading embedded TIFF file ...\n")); maxOffset = 0; fileOffset = (DWORD)(pFileHdr - pFileView); // // Each iteration of the following loops processes one IFD // from an embedded TIFF file. // do { PTIFF_IFD pIfd; PTIFF_TAG pIfdEntry; INT ifdCount; DWORD size, index, stripCount = 0; PDWORD pStripOffsets = NULL; pIfd = (PTIFF_IFD) (pFileHdr + ifdOffset); Assert( (PBYTE) pIfd < pFileEnd); if ((PBYTE) pIfd >= pFileEnd) { result = FAXERR_FATAL; __leave; } ifdOffset += sizeof(WORD) + pIfd->wEntries * sizeof(TIFF_TAG); pIfdOffset = pFileHdr + ifdOffset; Assert(pIfdOffset < pFileEnd); if (pIfdOffset >= pFileEnd) { result = FAXERR_FATAL; __leave; } if ((ifdOffset + sizeof(DWORD)) > maxOffset) maxOffset = ifdOffset + sizeof(DWORD); // // We should add the file offset to any non-zero IFD offset // if ((ifdOffset = ReadUnalignedDWord(pIfdOffset)) != 0) WriteUnalignedDWord(pIfdOffset, ifdOffset + fileOffset); // // Now go through each IFD entry and calculate the largest offset // pIfdEntry = (PTIFF_TAG) ((PBYTE) pIfd + sizeof(WORD)); ifdCount = pIfd->wEntries; Verbose((" Reading IFD: %d entries ...\n", ifdCount)); while (ifdCount-- > 0) { // // Figure the size of various TIFF data types // switch (pIfdEntry->DataType) { case TIFF_ASCII: case TIFF_BYTE: case TIFF_SBYTE: case TIFF_UNDEFINED: size = 1; break; case TIFF_SHORT: case TIFF_SSHORT: size = 2; break; case TIFF_LONG: case TIFF_SLONG: case TIFF_FLOAT: size = 4; break; case TIFF_RATIONAL: case TIFF_SRATIONAL: case TIFF_DOUBLE: size = 8; break; default: Warning(("Unknown TIFF data type: %d\n", pIfdEntry->DataType)); size = 1; break; } // // Look for StripOffsets and StripByteCounts tags // if (pIfdEntry->TagId == TIFFTAG_STRIPOFFSETS || pIfdEntry->TagId == TIFFTAG_STRIPBYTECOUNTS) { DWORD n = pIfdEntry->DataCount; if ((pIfdEntry->DataType == TIFF_LONG) && (stripCount == 0 || stripCount == n) && (pStripOffsets || (pStripOffsets = MemAllocZ(sizeof(DWORD)*n)))) { if ((stripCount = n) == 1) { pStripOffsets[0] += pIfdEntry->DataOffset; if (pIfdEntry->TagId == TIFFTAG_STRIPOFFSETS) pIfdEntry->DataOffset += fileOffset; } else { DWORD UNALIGNED *p; Verbose(("Multiple strips per page: %d\n", n)); p = (DWORD UNALIGNED *) (pFileHdr + pIfdEntry->DataOffset); for (index=0; index < stripCount; index++) { n = *p; pStripOffsets[index] += n; if (pIfdEntry->TagId == TIFFTAG_STRIPOFFSETS) *p = n + fileOffset; p = (DWORD UNALIGNED *) ((LPBYTE) p + sizeof(DWORD)); } } } else Error(("Bad StripOffsets/StripByteCounts tag\n")); } // // For a composite value, IFDENTRY.value is an offset // if (size * pIfdEntry->DataCount > sizeof(DWORD)) { if (pIfdEntry->DataOffset > maxOffset) maxOffset = pIfdEntry->DataOffset; pIfdEntry->DataOffset += fileOffset; } pIfdEntry++; } // // Make sure to skip the image data when search for the next file // if (pStripOffsets) { for (index=0; index < stripCount; index++) { if (pStripOffsets[index] > maxOffset) maxOffset = pStripOffsets[index]; } MemFree(pStripOffsets); } } while (ifdOffset); // // Search for the beginning of next TIFF file // pFileHdr += maxOffset; while (pFileHdr < pFileEnd) { if (ValidTiffFileHeader(pFileHdr)) { // // Modify the offset in the last IFD // ifdOffset = ReadUnalignedDWord(pFileHdr + sizeof(DWORD)); WriteUnalignedDWord(pIfdOffset, ifdOffset + (DWORD)(pFileHdr - pFileView)); break; } pFileHdr++; } } while (pFileHdr < pFileEnd); result = FAXERR_NONE; } } __finally { // // Perform necessary cleanup before returning to caller // if (pFileView) UnmapViewOfFile(pFileView); if (hFileMap) CloseHandle(hFileMap); CloseHandle(pFaxPort->hFile); pFaxPort->hFile = INVALID_HANDLE_VALUE; } return result; } INT CheckJobRestart( PFAXPORT pFaxPort ) /*++ Routine Description: Check if the job has been restarted. If not, get the ID of the next job in the chain. Arguments: pFaxPort - Points to a fax port structure Return Value: FAXERR_RESTART or FAXERR_NONE --*/ { JOB_INFO_3 *pJobInfo3; JOB_INFO_2 *pJobInfo2; INT status = FAXERR_NONE; // // If not, get the ID of the next job in the chain. // Verbose(("Job chain: id = %d\n", pFaxPort->nextJobId)); if (pJobInfo3 = MyGetJob(pFaxPort->hPrinter, 3, pFaxPort->jobId)) { pFaxPort->nextJobId = pJobInfo3->NextJobId; MemFree(pJobInfo3); } else pFaxPort->nextJobId = 0; // // Determine whether the job has been restarted or deleted // if (pJobInfo2 = MyGetJob(pFaxPort->hPrinter, 2, pFaxPort->jobId)) { if (pJobInfo2->Status & (JOB_STATUS_RESTART | JOB_STATUS_DELETING)) status = FAXERR_RESTART; MemFree(pJobInfo2); } return status; } BOOL FaxMonEndDocPort( HANDLE hPort ) /*++ Routine Description: Spooler calls this function at the end of a print job Arguments: hPort - Identifies the port Return Value: TRUE if successful, FALSE if there is an error --*/ { PFAXPORT pFaxPort = (PFAXPORT) hPort; INT status; LPTSTR pAtSign, pNewRecipName = NULL; //HANDLE hToken; DWORD FaxJobId; BOOL Rslt; JOB_INFO_2 *pJobInfo2; Trace("EndDocPort"); // // Make sure we have a valid handle // if (! ValidFaxPort(pFaxPort) || ! pFaxPort->hFaxSvc) { Error(("EndDocPort is given an invalid fax port handle\n")); SetLastError(ERROR_INVALID_HANDLE); return FALSE; } // // Check if the job has been restarted. If not, get the ID of // the next job in the chain. // if ((status = CheckJobRestart(pFaxPort)) != FAXERR_NONE) goto ExitEndDocPort; // // Check if we're at the end of a job chain // if (pFaxPort->nextJobId != 0 && pFaxPort->pParameters != NULL) { SetJob(pFaxPort->hPrinter, pFaxPort->jobId, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER); return TRUE; } // // The monitor runs in the context of the current job's owner. // In order to create temporary files in the spool directory, // we need to revert to the spooler context first. // /* if (! (hToken = RevertToPrinterSelf())) Error(("RevertToPrinterSelf failed: %d\n", GetLastError())); */ // // Check if we're dealing with fax jobs from win31 or win95 clients // if ((pFaxPort->pParameters == NULL) && (status = ProcessDownlevelFaxJob(pFaxPort)) != FAXERR_NONE) { goto ExitEndDocPort; } // // Fix up the temporary fax data into a properly formatted TIFF file. // if ((status = FixUpFaxFile(pFaxPort)) != FAXERR_NONE) { goto ExitEndDocPort; } // // Call the fax service to send the TIFF file // #if DBG if (_debugLevel > 0) { DbgPrint("Send document to fax service:\n"); DbgPrint(" Printer Name: %ws\n", pFaxPort->pPrinterName); DbgPrint(" Job ID: %d\n", pFaxPort->jobId); DbgPrint(" File Name: %ws\n", pFaxPort->pFilename); DbgPrint(" Recipient Number: %ws\n", pFaxPort->jobParam.RecipientNumber); DbgPrint(" Recipient Name: %ws\n", pFaxPort->jobParam.RecipientName); DbgPrint(" TSID: %ws\n", pFaxPort->jobParam.Tsid); DbgPrint(" Sender Name: %ws\n", pFaxPort->jobParam.SenderName); DbgPrint(" Sender Company: %ws\n", pFaxPort->jobParam.SenderCompany); DbgPrint(" Sender Dept: %ws\n", pFaxPort->jobParam.SenderDept); DbgPrint(" Billing Code: %ws\n", pFaxPort->jobParam.BillingCode); } #endif // // fixup the fax address // if (pAtSign = _tcschr(pFaxPort->jobParam.RecipientNumber, TEXT('@'))) { *pAtSign++ = NUL; if (pFaxPort->jobParam.RecipientName == NULL) pNewRecipName = (LPTSTR) pFaxPort->jobParam.RecipientName = (LPTSTR)DuplicateString(pFaxPort->jobParam.RecipientNumber); _tcscpy((LPTSTR)pFaxPort->jobParam.RecipientNumber, pAtSign); } // // send the fax // pJobInfo2 = MyGetJob( pFaxPort->hPrinter, 2, pFaxPort->jobId ); if (pJobInfo2) { pFaxPort->jobParam.DocumentName = pJobInfo2->pDocument; } else { pFaxPort->jobParam.DocumentName = NULL; } /* if (hToken && !ImpersonatePrinterClient(hToken)) { Error(("ImpersonatePrinterClient failed: %d\n", GetLastError())); } */ pFaxPort->jobParam.Reserved[0] = 0xffffffff; pFaxPort->jobParam.Reserved[1] = pFaxPort->jobId; Rslt = pFaxPort->pFaxSendDocumentW( pFaxPort->hFaxSvc, pFaxPort->pFilename, &pFaxPort->jobParam, NULL, &FaxJobId ); /* if (! (hToken = RevertToPrinterSelf())) { Error(("RevertToPrinterSelf failed: %d\n", GetLastError())); } */ if (pJobInfo2) { MemFree( pJobInfo2 ); pFaxPort->jobParam.DocumentName = NULL; } if (Rslt) { status = FAXERR_NONE; SetJob(pFaxPort->hPrinter, pFaxPort->jobId, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER); DeleteFile( pFaxPort->pFilename ); } else { status = GetLastError(); Error(("FaxSendDocumentForSpooler failed: %d\n", GetLastError())); } ExitEndDocPort: if (status == FAXERR_NONE) { // // If the job was successfully sent to the fax service, then // the service will delete the temporary file when it's done // with it. So we don't need to delete it here. // MemFree(pFaxPort->pFilename); pFaxPort->pFilename = NULL; } else { // // If the job wasn't successfully sent to the fax service, // inform the spooler that there is an error on the job. // // Or if the print job has no data, simply ignore it. // switch (status) { case FAXERR_RESTART: Warning(("Job restarted or deleted: id = %d\n", pFaxPort->jobId)); case FAXERR_IGNORE: SetJob(pFaxPort->hPrinter, pFaxPort->jobId, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER); break; default: Error(("Error sending fax job: id = %d\n", pFaxPort->jobId)); SetJob(pFaxPort->hPrinter, pFaxPort->jobId, 0, NULL, JOB_CONTROL_PAUSE); SetJobStatus(pFaxPort->hPrinter, pFaxPort->jobId, status); break; } } if (pNewRecipName) { MemFree(pNewRecipName); pFaxPort->jobParam.RecipientName = NULL; } FreeFaxJobInfo(pFaxPort); // // Switch back to the original context if necessary // /* if (hToken && !ImpersonatePrinterClient(hToken)) Error(("ImpersonatePrinterClient failed: %d\n", GetLastError()));*/ return (status < FAXERR_SPECIAL); } BOOL FaxMonWritePort( HANDLE hPort, LPBYTE pBuffer, DWORD cbBuf, LPDWORD pcbWritten ) /*++ Routine Description: Writes data to a port Arguments: hPort - Identifies the port pBuffer - Points to a buffer that contains data to be written to the port cbBuf - Specifies the size in bytes of the buffer pcbWritten - Returns the count of bytes successfully written to the port Return Value: TRUE if successful, FALSE if there is an error --*/ { PFAXPORT pFaxPort = (PFAXPORT) hPort; // // Make sure we have a valid handle // if (! ValidFaxPort(pFaxPort) || ! pFaxPort->hFaxSvc) { Error(("WritePort is given an invalid fax port handle\n")); SetLastError(ERROR_INVALID_HANDLE); return FALSE; } Assert(pFaxPort->hFile != INVALID_HANDLE_VALUE); return WriteFile(pFaxPort->hFile, pBuffer, cbBuf, pcbWritten, NULL); } BOOL FaxMonReadPort( HANDLE hPort, LPBYTE pBuffer, DWORD cbBuf, LPDWORD pcbRead ) /*++ Routine Description: Reads data from the port Arguments: hPort - Identifies the port pBuffer - Points to a buffer where data read from the printer can be written cbBuf - Specifies the size in bytes of the buffer pointed to by pBuffer pcbRead - Returns the number of bytes successfully read from the port Return Value: TRUE if successful, FALSE if there is an error --*/ { Trace("ReadPort"); SetLastError(ERROR_NOT_SUPPORTED); return FALSE; } BOOL FaxMonEnumPorts( LPTSTR pServerName, DWORD Level, LPBYTE pPorts, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pReturned ) /*++ Routine Description: Enumerates the ports available on the specified server Arguments: pServerName - Specifies the name of the server whose ports are to be enumerated dwLevel - Specifies the version of the structure to which pPorts points pPorts - Points to an array of PORT_INFO_1 structures where data describing the available ports will be writteno cbBuf - Specifies the size in bytes of the buffer to which pPorts points pcbNeeded - Returns the required buffer size identified by pPorts pReturned - Returns the number of PORT_INFO_1 structures returned Return Value: TRUE if successful, FALSE if there is an error --*/ #define MAX_DESC_LEN 64 { TCHAR portDescStr[MAX_DESC_LEN]; INT descStrSize, faxmonNameSize; DWORD cbNeeded; BOOL status = TRUE; PORT_INFO_1 *pPortInfo1 = (PORT_INFO_1 *) pPorts; PORT_INFO_2 *pPortInfo2 = (PORT_INFO_2 *) pPorts; INT strSize; Trace("EnumPorts"); if (pcbNeeded == NULL || pReturned == NULL || (pPorts == NULL && cbBuf != 0)) { Error(("Invalid input parameters\n")); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // // Load the fax port description string // if (! LoadString(ghInstance, IDS_FAX_PORT_DESC, portDescStr, MAX_DESC_LEN)) portDescStr[0] = NUL; descStrSize = SizeOfString(portDescStr); faxmonNameSize = SizeOfString(faxMonitorName); switch (Level) { case 1: cbNeeded = sizeof(PORT_INFO_1) + SizeOfString(FAX_PORT_NAME); break; case 2: cbNeeded = sizeof(PORT_INFO_2) + descStrSize + faxmonNameSize + SizeOfString(FAX_PORT_NAME); break; } *pReturned = 1; *pcbNeeded = cbNeeded; if (cbNeeded > cbBuf) { // // Caller didn't provide a big enough buffer // SetLastError(ERROR_INSUFFICIENT_BUFFER); status = FALSE; } else { // // Strings must be packed at the end of the caller provided buffer. // Otherwise, spooler will mess up. // pPorts += cbBuf; // // Copy the requested port information to the caller provided buffer // strSize = SizeOfString(FAX_PORT_NAME); pPorts -= strSize; CopyMemory(pPorts, FAX_PORT_NAME, strSize); switch (Level) { case 1: pPortInfo1->pName = (LPTSTR) pPorts; Verbose(("Port info 1: %ws\n", pPortInfo1->pName)); pPortInfo1++; break; case 2: pPortInfo2->pPortName = (LPTSTR) pPorts; // // Copy the fax monitor name string // pPorts -= faxmonNameSize; pPortInfo2->pMonitorName = (LPTSTR) pPorts; CopyMemory(pPorts, faxMonitorName, faxmonNameSize); // // Copy the fax port description string // pPorts -= descStrSize; pPortInfo2->pDescription = (LPTSTR) pPorts; CopyMemory(pPorts, portDescStr, descStrSize); pPortInfo2->fPortType = PORT_TYPE_WRITE; pPortInfo2->Reserved = 0; Verbose(("Port info 2: %ws, %ws, %ws\n", pPortInfo2->pPortName, pPortInfo2->pMonitorName, pPortInfo2->pDescription)); pPortInfo2++; break; } } return status; } BOOL DisplayErrorNotImplemented( HWND hwnd, INT titleId ) /*++ Routine Description: Display an error dialog to tell the user that he cannot manage fax devices in the Printers folder. Arguments: hwnd - Specifies the parent window for the message box titleId - Message box title string resource ID Return Value: FALSE --*/ { TCHAR title[128]; TCHAR message[256]; LoadString(ghInstance, titleId, title, 128); LoadString(ghInstance, IDS_CONFIG_ERROR, message, 256); MessageBox(hwnd, message, title, MB_OK|MB_ICONERROR); SetLastError(ERROR_SUCCESS); return FALSE; } BOOL FaxMonAddPort( LPTSTR pServerName, HWND hwnd, LPTSTR pMonitorName ) /*++ Routine Description: Adds the name of a port to the list of supported ports Arguments: pServerName - Specifies the name of the server to which the port is to be added hwnd - Identifies the parent window of the AddPort dialog box pMonitorName - Specifies the monitor associated with the port Return Value: TRUE if successful, FALSE if there is an error --*/ { Trace("AddPort"); return DisplayErrorNotImplemented(hwnd, IDS_ADD_PORT); } BOOL FaxMonAddPortEx( LPTSTR pServerName, DWORD level, LPBYTE pBuffer, LPTSTR pMonitorName ) /*++ Routine Description: Adds the name of a port to the list of supported ports Arguments: pServerName - Specifies the name of the server to which the port is to be added hwnd - Identifies the parent window of the AddPort dialog box pMonitorName - Specifies the monitor associated with the port Return Value: TRUE if successful, FALSE if there is an error --*/ { Trace("AddPortEx"); SetLastError(ERROR_NOT_SUPPORTED); return FALSE; } BOOL FaxMonDeletePort( LPTSTR pServerName, HWND hwnd, LPTSTR pPortName ) /*++ Routine Description: Delete the specified port from the list of supported ports Arguments: pServerName - Specifies the name of the server from which the port is to be removed hwnd - Identifies the parent window of the port-deletion dialog box pPortName - Specifies the name of the port to be deleted Return Value: TRUE if successful, FALSE if there is an error --*/ { Trace("DeletePort"); return DisplayErrorNotImplemented(hwnd, IDS_CONFIGURE_PORT); } BOOL FaxMonConfigurePort( LPWSTR pServerName, HWND hwnd, LPWSTR pPortName ) /*++ Routine Description: Display a dialog box to allow user to configure the specified port Arguments: pServerName - Specifies the name of the server on which the given port exists hwnd - Identifies the parent window of the port-configuration dialog pPortName - Specifies the name of the port to be configured Return Value: TRUE if successful, FALSE if there is an error --*/ { Trace("ConfigurePort"); return DisplayErrorNotImplemented(hwnd, IDS_CONFIGURE_PORT); } LPTSTR DuplicateString( LPCTSTR pSrcStr ) /*++ Routine Description: Make a duplicate of the given character string Arguments: pSrcStr - Specifies the string to be duplicated Return Value: Pointer to the duplicated string, NULL if there is an error --*/ { LPTSTR pDestStr; INT strSize; if (pSrcStr != NULL) { strSize = SizeOfString(pSrcStr); if (pDestStr = MemAlloc(strSize)) CopyMemory(pDestStr, pSrcStr, strSize); else Error(("Memory allocation failed\n")); } else pDestStr = NULL; return pDestStr; } PVOID MyGetJob( HANDLE hPrinter, DWORD level, DWORD jobId ) /*++ Routine Description: Wrapper function for spooler API GetJob Arguments: hPrinter - Handle to the printer object level - Level of JOB_INFO structure interested jobId - Specifies the job ID Return Value: Pointer to a JOB_INFO structure, NULL if there is an error --*/ { PBYTE pJobInfo = NULL; DWORD cbNeeded; if (!GetJob(hPrinter, jobId, level, NULL, 0, &cbNeeded) && GetLastError() == ERROR_INSUFFICIENT_BUFFER && (pJobInfo = MemAlloc(cbNeeded)) && GetJob(hPrinter, jobId, level, pJobInfo, cbNeeded, &cbNeeded)) { return pJobInfo; } Error(("GetJob failed: %d\n", GetLastError())); MemFree(pJobInfo); return NULL; } BOOL SetJobStatus( HANDLE hPrinter, DWORD jobId, INT statusStrId ) /*++ Routine Description: Update the status information of a print job Arguments: hPrinter - Specifies the printer on which the job is printed jobId - Specifies the job identifier statusStrID - Specifies the status string resource ID Return Value: TRUE if successful, FALSE if there is an error --*/ #define MAX_MESSAGE_LEN 256 { JOB_INFO_1 *pJobInfo1; BOOL result = FALSE; TCHAR message[MAX_MESSAGE_LEN]; // // Get the current job information // if (pJobInfo1 = MyGetJob(hPrinter, 1, jobId)) { // // Update the status field // if (LoadString(ghInstance, statusStrId, message, MAX_MESSAGE_LEN)) pJobInfo1->pStatus = message; else { pJobInfo1->pStatus = NULL; pJobInfo1->Status = JOB_STATUS_ERROR; } pJobInfo1->Position = JOB_POSITION_UNSPECIFIED; if (! (result = SetJob(hPrinter, jobId, 1, (PBYTE) pJobInfo1, 0))) Error(("SetJob failed: %d\n", GetLastError())); MemFree(pJobInfo1); } return result; } DWORD GetRegistryDWord( HKEY hRegKey, LPTSTR pValueName, DWORD defaultValue ) /*++ Routine Description: Retrieve a DWORD value from the registry Arguments: hRegKey - Handle to the user info registry key pValueName - Specifies the name of the string value in registry defaultValue - Specifies the default value to be used in case of an error Return Value: Requested DWORD value from the user info registry key --*/ { DWORD size, type, value; // // Retrieve the country code value from the registry. // Use the default value if none exists. // size = sizeof(value); if (RegQueryValueEx(hRegKey, pValueName, NULL, &type, (PBYTE) &value, &size) != ERROR_SUCCESS || type != REG_DWORD) { value = defaultValue; } return value; } #if DBG // // Variable for controlling the amount of debug messages generated // INT _debugLevel = 1; LPCSTR StripDirPrefixA( LPCSTR pFilename ) /*++ Routine Description: Strip the directory prefix off a filename Arguments: pFilename - Pointer to filename string Return Value: Pointer to the last component of a filename (without directory prefix) --*/ { LPCSTR pstr; if (pstr = strrchr(pFilename, PATH_SEPARATOR)) return pstr + 1; return pFilename; } #endif