/*++ Copyright (c) 1996 Microsoft Corporation Module Name: print.c Abstract: This module handles the FAX receive case. Author: Wesley Witt (wesw) 24-April-1996 Revision History: --*/ #include "faxsvc.h" #pragma hdrstop #define INTERNAL 1 #include "common.h" PFAX_PRINTER_INFO FaxPrinterInfo; DWORD FaxPrinters; PHANDLE FaxPrinterNotifyHandles; DWORD HandleCount; HANDLE SpoolerProcessHandle; DWORD SpoolerProcessIdx; DWORD ReservedHandles; HANDLE DirtyTimerHandle = INVALID_HANDLE_VALUE; DWORD DirtyTimerIdx; HANDLE ModemTimerHandle = INVALID_HANDLE_VALUE; DWORD ModemTimerIdx; extern DWORD FaxDirtyDays; extern HANDLE FaxServerEvent; LPTSTR PrintPlatforms[] = { TEXT("Windows NT x86"), TEXT("Windows NT R4000"), TEXT("Windows NT Alpha_AXP"), TEXT("Windows NT PowerPC") }; WORD PrinterFieldType1[] = { JOB_NOTIFY_FIELD_STATUS }; WORD PrinterFieldType2[] = { PRINTER_NOTIFY_FIELD_PRINTER_NAME }; PRINTER_NOTIFY_OPTIONS_TYPE PrinterNotifyOptionsType[] = { { JOB_NOTIFY_TYPE, 0, 0, 0, sizeof(PrinterFieldType1) / sizeof(WORD), PrinterFieldType1 }, { PRINTER_NOTIFY_TYPE, 0, 0, 0, sizeof(PrinterFieldType2) / sizeof(WORD), PrinterFieldType2 } }; PRINTER_NOTIFY_OPTIONS PrinterNotifyOptions = { 2, 0, sizeof(PrinterNotifyOptionsType) / sizeof(PRINTER_NOTIFY_OPTIONS_TYPE), PrinterNotifyOptionsType }; BOOL AddPortExW( LPWSTR pName, DWORD Level, LPBYTE pBuffer, LPWSTR pMonitorName ); VOID CleanDirtyQueues( VOID ); PVOID MyEnumPrinters( LPTSTR pServerName, DWORD level, PDWORD pcPrinters ) /*++ Routine Description: Wrapper function for spooler API EnumPrinters Arguments: pServerName - Specifies the name of the print server level - Level of PRINTER_INFO_x structure pcPrinters - Returns the number of printers enumerated Return Value: Pointer to an array of PRINTER_INFO_x structures NULL if there is an error --*/ { PBYTE pPrinterInfo = NULL; DWORD cb; if (! EnumPrinters(PRINTER_ENUM_LOCAL, pServerName, level, NULL, 0, &cb, pcPrinters) && GetLastError() == ERROR_INSUFFICIENT_BUFFER && (pPrinterInfo = MemAlloc(cb)) && EnumPrinters(PRINTER_ENUM_LOCAL, pServerName, level, pPrinterInfo, cb, &cb, pcPrinters)) { return pPrinterInfo; } MemFree(pPrinterInfo); return NULL; } PVOID MyEnumPorts( LPTSTR pServerName, DWORD level, PDWORD pcPorts ) /*++ Routine Description: Wrapper function for spooler API EnumPrinters Arguments: pServerName - Specifies the name of the print server level - Level of PRINTER_INFO_x structure pcPrinters - Returns the number of printers enumerated Return Value: Pointer to an array of PRINTER_INFO_x structures NULL if there is an error --*/ { PBYTE pPortInfo = NULL; DWORD cb; if (! EnumPorts( NULL, level, NULL, 0, &cb, pcPorts ) && GetLastError() == ERROR_INSUFFICIENT_BUFFER && (pPortInfo = MemAlloc(cb)) && EnumPorts( NULL, level, pPortInfo, cb, &cb, pcPorts )) { return pPortInfo; } MemFree( pPortInfo ); return NULL; } 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; } MemFree(pJobInfo); return NULL; } DWORD GetPrinterDataDWord( HANDLE hPrinter, PWSTR pRegKey, DWORD defaultValue ) /*++ Routine Description: Retrieve a DWORD value under PrinterData registry key Arguments: hPrinter - Specifies the printer in question pRegKey - Specifies the name of registry value defaultValue - Specifies the default value to be used if no data exists in registry Return Value: Current value for the requested registry key --*/ { DWORD value, type, cb; if (GetPrinterData(hPrinter, pRegKey, &type, (PBYTE) &value, sizeof(value), &cb) == ERROR_SUCCESS) { return value; } return defaultValue; } LPTSTR GetPrinterDataStr( HANDLE hPrinter, LPTSTR pRegKey ) /*++ Routine Description: Get a string value from the PrinterData registry key Arguments: hPrinter - Identifies the printer object pRegKey - Specifies the name of registry value Return Value: pBuffer --*/ { DWORD type, cb; PVOID pBuffer = NULL; // // We should really pass NULL for pData parameter here. But to workaround // a bug in the spooler API GetPrinterData, we must pass in a valid pointer here. // if (GetPrinterData( hPrinter, pRegKey, &type, (PBYTE) &type, 0, &cb ) == ERROR_MORE_DATA && (pBuffer = MemAlloc( cb )) && GetPrinterData( hPrinter, pRegKey, &type, pBuffer, cb, &cb ) == ERROR_SUCCESS && (type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ)) { return pBuffer; } DebugPrint(( TEXT("Couldn't get printer data string %ws: %d\n"), pRegKey, GetLastError() )); MemFree( pBuffer ); return NULL; } BOOL DeletePortInternal( HANDLE hPrinter, LPTSTR PortName ) { BOOL Rval = TRUE; BOOL PortFound = FALSE; DWORD i; LPPRINTER_INFO_2 PrinterInfo = NULL; LPTSTR p; LPTSTR s2; LPTSTR s; if ((!GetPrinter( hPrinter, 2, NULL, 0, &i )) && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { DebugPrint(( TEXT("GetPrinter() failed, ec=%d"), GetLastError() )); Rval = FALSE; goto exit; } PrinterInfo = (LPPRINTER_INFO_2) MemAlloc( i ); if (!PrinterInfo) { DebugPrint(( TEXT("MemAlloc() failed, size=%d"), i )); Rval = FALSE; goto exit; } if (!GetPrinter( hPrinter, 2, (LPBYTE) PrinterInfo, i, &i )) { DebugPrint(( TEXT("GetPrinter() failed, ec=%d"), GetLastError() )); Rval = FALSE; goto exit; } p = PrinterInfo->pPortName; while (p && *p) { s = _tcschr( p, TEXT(',') ); if (s) { s2 = s; *s = 0; } else { s2 = NULL; } if (_tcscmp( p, PortName ) == 0) { PortFound = TRUE; if (s2) { _tcscpy( p, s2+1 ); } else { *p = 0; break; } } else { p += _tcslen(p); if (s2) { *s2 = TEXT(','); p += 1; } } } if (PortFound) { if (!SetPrinter( hPrinter, 2, (LPBYTE) PrinterInfo, 0 )) { DebugPrint(( TEXT("SetPrinter() failed, ec=%d"), GetLastError() )); goto exit; } } exit: MemFree( PrinterInfo ); return Rval; } BOOL DeletePrinterPort( LPTSTR PortName ) { BOOL Rval = TRUE; DWORD i; PPRINTER_INFO_2 PrinterInfo = NULL; DWORD PrinterCount; PRINTER_DEFAULTS PrinterDefaults; HANDLE hPrinter; PrinterInfo = MyEnumPrinters( NULL, 2, &PrinterCount ); if (!PrinterInfo) { DebugPrint(( TEXT("MyEnumPrinters() failed, ec=%d"), GetLastError() )); return FALSE; } // // first remove the port name from the list // associated with each fax printer. this is // necessary because the DeletePort() api will // not work if the port is associated with a // printer // for (i=0; ipPortName = NewPort; if (!SetPrinter( hPrinter, 2, (LPBYTE) ThisPrinterInfo, 0 )) { DebugPrint(( TEXT("SetPrinter() failed, ec=%d"), GetLastError() )); Rval = FALSE; goto exit; } ClosePrinter( hPrinter ); } } exit: MemFree( ThisPrinterInfo ); MemFree( PrinterInfo ); MemFree( PortInfo2 ); MemFree( NewPort ); return Rval; } BOOL CreateNullPrintJobs( PJOB_ENTRY JobEntry ) /*++ Routine Description: Creates a NULL print job on each FAX printer in the system. This is necessary to that status information is displayed for incoming fax jobs. Arguments: JobEntry - Pointer to a FAX job entry. Return Value: TRUE - The print jobs are all created. FALSE - Some or all of the print jobs were not created. --*/ { DWORD i; DOC_INFO_1 DocInfo; BOOL Rval = TRUE; PRINTER_DEFAULTS PrinterDefaults; // // loop thru the printers and create a job on each one // EnterCriticalSection( &CsJob ); for (i=0; ihPrinter[i], &PrinterDefaults )) { DebugPrint(( TEXT("OpenPrinter() failed, ec=%d"), GetLastError() )); Rval = FALSE; goto exit; } JobEntry->PrintJobIds[i] = StartDocPrinter( JobEntry->hPrinter[i], 1, (LPBYTE) &DocInfo ); if (JobEntry->PrintJobIds[i]) { DebugPrint((TEXT("Started receive print JobId %d"), JobEntry->PrintJobIds[i])); // // pause the job so nothing really happens // if (!SetJob( FaxPrinterInfo[i].hPrinter, JobEntry->PrintJobIds[i], 0, NULL, JOB_CONTROL_PAUSE )) { DebugPrint(( TEXT("SetJob() failed, ec=%d"), GetLastError() )); } // // set the initial status string // SetPrintJobStatus( JobEntry->hPrinter[i], JobEntry->PrintJobIds[i], FPS_INITIALIZING, NULL, -1 ); } else { DebugPrint(( TEXT("StartDocPrinter() failed, ec=%d"), GetLastError() )); Rval = FALSE; } } exit: LeaveCriticalSection( &CsJob ); return Rval; } BOOL DeleteNullPrintJobs( PFAX_PRINTER_INFO RecvFaxPrinterInfo ) /*++ Routine Description: Deletes the NULL print jobs for all FAX printers on the system. Arguments: RecvFaxPrinterInfo - Pointer to array of structs holding printer handles to close. Return Value: TRUE - The print jobs are all deleted. FALSE - Some or all of the print jobs were not deleted. --*/ { DWORD i; BOOL Rval = TRUE; DWORD JobId; HANDLE hPrinter; EnterCriticalSection( &CsJob ); for (i=0; iReceiverName && (FaxSend->ReceiverName[0] != wcZero) ) { MsTagInfo.RecipName = FaxSend->ReceiverName; } MsTagInfo.RecipNumber = NULL; if (FaxSend->ReceiverNumber && (FaxSend->ReceiverNumber[0] != wcZero) ) { MsTagInfo.RecipNumber = FaxSend->ReceiverNumber; } MsTagInfo.SenderName = NULL; if (FaxSend->CallerName && (FaxSend->CallerName[0] != wcZero) ) { MsTagInfo.SenderName = FaxSend->CallerName; } MsTagInfo.Routing = NULL; if (FaxStatus->RoutingInfo && (FaxStatus->RoutingInfo[0] != wcZero) ) { MsTagInfo.Routing = FaxStatus->RoutingInfo; } MsTagInfo.CallerId = NULL; if (FaxStatus->CallerId && (FaxStatus->CallerId[0] != wcZero) ) { MsTagInfo.CallerId = FaxStatus->CallerId; } MsTagInfo.Csid = NULL; if (FaxStatus->CSI && (FaxStatus->CSI[0] != wcZero) ) { MsTagInfo.Csid = FaxStatus->CSI; } MsTagInfo.Tsid = NULL; if (FaxSend->CallerNumber && (FaxSend->CallerNumber[0] != wcZero) ) { MsTagInfo.Tsid = FaxSend->CallerNumber; } MsTagInfo.FaxTime = SendTime; TiffAddMsTags( ArchiveFileName, &MsTagInfo ); } } if (rVal) { FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MAX, 2, MSG_FAX_ARCHIVE_SUCCESS, FaxFileName, ArchiveFileName ); } else { FaxLog( FAXLOG_CATEGORY_OUTBOUND, FAXLOG_LEVEL_MIN, 3, MSG_FAX_ARCHIVE_FAILED, FaxFileName, ArchiveFileName, GetLastErrorText(GetLastError()) ); } exit: MemFree( ArchiveDirStr ); MemFree( ArchiveDir ); MemFree( ArchiveFileName ); return rVal; } BOOL RestartPrintJob( HANDLE hPrinter, DWORD PrintJobId ) /*++ Routine Description: Causes a print job to be restarted. Arguments: hPrinter - Handle to printer that owns the job PrintJobId - Id of the job to be restarted Return Value: TRUE - The print job is restarted FALSE - The print job is not restarted --*/ { PJOB_INFO_2 pJobInfo; SYSTEMTIME SystemTime; DWORD Minutes; if (!PrintJobId) { return FALSE; } DebugPrint((TEXT("Setting job status for job %d - JOB_CONTROL_RESTART"), PrintJobId)); pJobInfo = (PJOB_INFO_2) MyGetJob( hPrinter, 2, PrintJobId ); if (pJobInfo == NULL) { return FALSE; } GetSystemTime(&SystemTime); // wait a couple of minutes to restart the job Minutes = SystemTime.wHour * MINUTES_PER_HOUR + SystemTime.wMinute; Minutes += 2; Minutes %= MINUTES_PER_DAY; pJobInfo->StartTime = Minutes; if (!SetJob( hPrinter, PrintJobId, 2, (LPBYTE) pJobInfo, JOB_CONTROL_RESTART )) { DebugPrint(( TEXT("SetJob() failed: 0x%08x"), GetLastError() )); MemFree( pJobInfo ); return FALSE; } MemFree( pJobInfo ); return TRUE; } BOOL SetPrintJobStatus( HANDLE hPrinter, DWORD PrintJobId, DWORD Status, LPTSTR PhoneNumber, INT PageCount ) /*++ Routine Description: Changes the status string for a print job. Arguments: PrinterName - Name of the printer that owns the job PrintJobId - Id of the job to be restarted Status - Status is Return Value: TRUE - The status is changed. FALSE - The status is NOT changed. --*/ { LPJOB_INFO_1 JobInfo = NULL; LPTSTR StatusString = NULL; BOOL Rval = FALSE; DWORD BytesNeeded; DWORD Size; if ((!GetJob( hPrinter, PrintJobId, 1, NULL, 0, &BytesNeeded )) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { DebugPrint(( TEXT("SetPrintJobStatus GetJob(0) JobId %d failed: 0x%08x"), PrintJobId, GetLastError() )); goto exit; } Size = BytesNeeded; BytesNeeded += 256; JobInfo = (LPJOB_INFO_1) MemAlloc( BytesNeeded ); if (!JobInfo) { DebugPrint(( TEXT("MemAlloc() failed: 0x%08x"), BytesNeeded )); goto exit; } if (!GetJob( hPrinter, PrintJobId, 1, (LPBYTE) JobInfo, Size, &Size )) { DebugPrint(( TEXT("SetPrintJobStatus GetJob(1) JobId %d failed: 0x%08x"), PrintJobId, GetLastError() )); goto exit; } StatusString = GetString( Status ); if (StatusString) { JobInfo->pStatus = (LPTSTR) ((LPBYTE)JobInfo + Size); if (Status == FS_DIALING || Status == FS_TRANSMITTING) { _stprintf( JobInfo->pStatus, StatusString, PhoneNumber ); } else { _tcscpy( JobInfo->pStatus, StatusString ); } } if (PageCount != -1) { JobInfo->PagesPrinted = (DWORD) PageCount; } DebugPrint((TEXT("Setting job status for job %d - %s"), PrintJobId, StatusString)); if (!SetJob( hPrinter, PrintJobId, 1, (LPBYTE) JobInfo, 0 )) { DebugPrint(( TEXT("SetJob() failed: 0x%08x"), GetLastError() )); goto exit; } Rval = TRUE; exit: if (JobInfo) { MemFree( JobInfo ); } return Rval; } BOOL IsPrinterFaxPrinter( LPTSTR PrinterName ) /*++ Routine Description: Determines if a printer is a fax printer. Arguments: PrinterName - Name of the printer Return Value: TRUE for success. FALSE for failure. --*/ { HANDLE hPrinter = NULL; PRINTER_DEFAULTS PrinterDefaults; SYSTEM_INFO SystemInfo; DWORD Size; DWORD Rval = FALSE; LPDRIVER_INFO_2 DriverInfo = NULL; PrinterDefaults.pDatatype = NULL; PrinterDefaults.pDevMode = NULL; PrinterDefaults.DesiredAccess = PRINTER_READ; if (!OpenPrinter( PrinterName, &hPrinter, &PrinterDefaults )) { DebugPrint(( TEXT("OpenPrinter(%d) failed, ec=%d"), __LINE__, GetLastError() )); return FALSE; } GetSystemInfo( &SystemInfo ); Size = 4096; DriverInfo = (LPDRIVER_INFO_2) MemAlloc( Size ); if (!DriverInfo) { DebugPrint(( TEXT("Memory allocation failed, size=%d"), Size )); goto exit; } Rval = GetPrinterDriver( hPrinter, PrintPlatforms[SystemInfo.wProcessorArchitecture], 2, (LPBYTE) DriverInfo, Size, &Size ); if (!Rval) { DebugPrint(( TEXT("GetPrinterDriver() failed, ec=%d"), GetLastError() )); goto exit; } if (_tcscmp( DriverInfo->pName, FAX_DRIVER_NAME ) == 0) { Rval = TRUE; } else { Rval = FALSE; } exit: MemFree( DriverInfo ); ClosePrinter( hPrinter ); return Rval; } BOOL RefreshPrinterInfo( VOID ) /*++ Routine Description: This function allocates the necessary data structures to track the fax printers on the server. The data structures are then populated with the necessary data. Arguments: None. Return Value: TRUE for success. FALSE for failure. --*/ { DWORD PrinterCount; PPRINTER_INFO_2 PrinterInfo; DWORD i; DWORD j; HANDLE hPrinter; HANDLE hNotify; PRINTER_DEFAULTS PrinterDefaults; BOOL Rval = FALSE; EnterCriticalSection( &CsJob ); // // close all handles and release all memory // if (FaxPrinterInfo) { for (i=0; iAborting) { // // either the job does not exist or it is already aborting // LeaveCriticalSection( &CsJob ); return FALSE; } SetPrintJobStatus( FaxPrinterInfo->hPrinter, JobId, FPS_ABORTING, NULL, -1 ); // // call the device provider's abort function // __try { JobEntry->LineInfo->Provider->FaxDevAbortOperation( (HANDLE) JobEntry->InstanceData ); } __except (EXCEPTION_EXECUTE_HANDLER) { JobEntry->ErrorCode = GetExceptionCode(); } JobEntry->Aborting = TRUE; LeaveCriticalSection( &CsJob ); return TRUE; } HANDLE GetSpoolerProcessHandle( VOID ) /*++ Routine Description: This function gets a handles to the spooler's process object. It does this by enumerating the task list on the system and then looks for a process called "spoolss.exe". This task's process identifier is used to open a process handle. Arguments: None. Return Value: NULL - Could not get the spooler's process handle HANDLE - The spooler's process handle --*/ { #define MAX_TASKS 256 DWORD TaskCount; PTASK_LIST TaskList = NULL; DWORD SpoolerPid = 0; DWORD i; HANDLE SpoolerProcessHandle = NULL; TaskList = (PTASK_LIST) MemAlloc( MAX_TASKS * sizeof(TASK_LIST) ); if (!TaskList) { goto exit; } TaskCount = GetTaskList( TaskList, MAX_TASKS ); if (!TaskCount) { goto exit; } for (i=0; i= HandleCount && WaitObject < MAXIMUM_WAIT_OBJECTS)) { // // there was some problem in receiving the event // DebugPrint(( TEXT("WaitForMultipleObjects() failed, ec=%d"), GetLastError() )); continue; } if (WaitObject == SpoolerProcessIdx) { // // the spooler just ended // SpoolerProcessHandle = 0; SpoolerProcessIdx = 0; WaitForSpoolerToStart(); RefreshPrinterInfo(); continue; } if (WaitObject == DirtyTimerIdx) { DWORD ThreadId; DWORD WaitObject; // // if the thread is still running, don't create another one // if (CleanQueueHandle != NULL) { WaitObject = WaitForSingleObject( CleanQueueHandle, 0 ); if (WaitObject == WAIT_TIMEOUT) { continue; } CloseHandle( CleanQueueHandle ); } CleanQueueHandle = CreateThread( NULL, 1024*100, (LPTHREAD_START_ROUTINE) CleanDirtyQueues, NULL, 0, &ThreadId ); if (CleanQueueHandle == NULL) { DebugPrint(( TEXT("Cannot create CleanDirtyQueues thread") )); } continue; } if (WaitObject == ModemTimerIdx) { PLIST_ENTRY Next; PLINE_INFO LineInfo; EnterCriticalSection( &CsLine ); Next = TapiLinesListHead.Flink; if (Next) { while ((ULONG)Next != (ULONG)&TapiLinesListHead) { LineInfo = CONTAINING_RECORD( Next, LINE_INFO, ListEntry ); Next = LineInfo->ListEntry.Flink; if (LineInfo->UnimodemDevice && (LineInfo->Flags & FPF_POWERED_OFF) && (LineInfo->Flags & FPF_RECEIVE_OK)) { // // put a popup on the currently active desktop // we only allow 1 popup per device at a time // and we only present the popup twice // if (!LineInfo->ModemInUse && LineInfo->ModemPopupActive && LineInfo->ModemPopUps < MAX_MODEM_POPUPS) { LineInfo->ModemPopupActive = 0; LineInfo->ModemPopUps += 1; ServiceMessageBox( GetString( IDS_POWERED_OFF_MODEM ), MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND, TRUE, &LineInfo->ModemPopupActive ); } // // see if we can revive the device // if (OpenTapiLine( LineInfo )) { LPLINEDEVSTATUS LineDevStatus; // // check to see if the line is in use // LineDevStatus = MyLineGetLineDevStatus( LineInfo->hLine ); if (LineDevStatus) { if (LineDevStatus->dwNumOpens > 0 && LineDevStatus->dwNumActiveCalls > 0) { LineInfo->ModemInUse = TRUE; } else { LineInfo->ModemInUse = FALSE; } MemFree( LineDevStatus ); } if (!LineInfo->ModemInUse) { DebugPrint(( TEXT("Device %s is now powered on, connected, and ready for use"), LineInfo->DeviceName )); LineInfo->Flags &= ~FPF_POWERED_OFF; LineInfo->Flags &= ~FPF_RECEIVE_OK; LineInfo->Flags |= FPF_RECEIVE; LineInfo->State = FPS_AVAILABLE; CreateFaxEvent( LineInfo->PermanentLineID, FEI_MODEM_POWERED_ON ); } } if (LineInfo->Flags & FPF_POWERED_OFF) { DebugPrint(( TEXT("Could not revive device [%s]"), LineInfo->DeviceName )); } } } } LeaveCriticalSection( &CsLine ); continue; } // // get the status information from the spooler // if (!FindNextPrinterChangeNotification( FaxPrinterNotifyHandles[WaitObject], &Change, NULL, &PrinterNotifyInfo )) { DebugPrint(( TEXT("FindNextPrinterChangeNotification() failed, ec=%d"), GetLastError() )); continue; } if (Change == PRINTER_CHANGE_ADD_PRINTER || Change == PRINTER_CHANGE_DELETE_PRINTER) { // // get the current printer info // RefreshPrinterInfo(); } else if (PrinterNotifyInfo && PrinterNotifyInfo->aData[0].Field == JOB_NOTIFY_FIELD_STATUS) { HandleJobChange( &FaxPrinterInfo[WaitObject], PrinterNotifyInfo->aData[0].NotifyData.adwData[0], PrinterNotifyInfo->aData[0].Id ); } // // free the spooler allocated memory // FreePrinterNotifyInfo( PrinterNotifyInfo ); } return 0; } VOID DisallowFaxSharing( VOID ) { HANDLE hPrinterServer; PRINTER_DEFAULTS PrinterDefaults; TCHAR String[128]; LONG Rslt; DWORD Size; PrinterDefaults.pDatatype = NULL; PrinterDefaults.pDevMode = NULL; PrinterDefaults.DesiredAccess = SERVER_ACCESS_ADMINISTER; if (!OpenPrinter( NULL, &hPrinterServer, &PrinterDefaults )) { DebugPrint(( TEXT("OpenPrinter() failed, ec=%d"), GetLastError() )); return; } _tcscpy( String, FAX_DRIVER_NAME ); Size = StringSize( String ); Rslt = SetPrinterData( hPrinterServer, SPLREG_NO_REMOTE_PRINTER_DRIVERS, REG_SZ, (LPBYTE) String, Size ); if ((Rslt != ERROR_SUCCESS) && (Rslt != ERROR_SUCCESS_RESTART_REQUIRED)) { DebugPrint(( TEXT("SetPrinterData() failed, ec=%d"), Rslt )); } ClosePrinter( hPrinterServer ); return; } BOOL InitializePrinting( VOID ) /*++ Routine Description: This function initializes the printing thread. The thread is used to handle the case where the user that started a fax send wants to delete the print job. Arguments: None. Return Value: TRUE for success. FALSE for failure. --*/ { DWORD ThreadId; HANDLE hThread; // // this shouldn't be necessary, but someone might // figure out how to subvert our security // if (InstallType & FAX_INSTALL_WORKSTATION) { DisallowFaxSharing(); } // // get the spooler's process handle // SpoolerProcessHandle = GetSpoolerProcessHandle(); // // get the current printer info // RefreshPrinterInfo(); // // start the thread that will do the actual // status processing // hThread = CreateThread( NULL, 1024*100, (LPTHREAD_START_ROUTINE) PrintStatusThread, NULL, 0, &ThreadId ); if (!hThread) { return GetLastError(); } CloseHandle( hThread ); return TRUE; } VOID CleanDirtyQueues( VOID ) /*++ Routine Description: This function is invoked periodically by PrintStatusThread to clean the fax printer queues of print jobs that have failed to be sent and have been in the queue longer than FaxDirtyDays. This function also attempts to route inbound faxes that have failed previous routing attempts. Arguments: None. Return Value: None. --*/ { #if 0 DWORD i; DWORD j; PJOB_INFO_2 JobInfo; BYTE JobBuffer[4096]; BOOL Result; DWORD cbData; DWORD cJobs; LARGE_INTEGER CurrentTime; LARGE_INTEGER SubmitTime; DWORD cBytes; DWORD Retries; LPTSTR RetryTag; LPTSTR RouteTag; DWORD WaitObject; // wait for the server to come up completely WaitObject = WaitForSingleObject( FaxServerEvent, INFINITE ); DebugPrint(( TEXT("Cleaning print queues") )); GetSystemTimeAsFileTime( (FILETIME *) &CurrentTime ); // enumerate all of the print jobs in all of the fax printers for (i = 0; i < FaxPrinters; i++) { for (j = 0; TRUE ; j++) { Result = EnumJobs( FaxPrinterInfo[i].hPrinter, j, 1, 2, JobBuffer, sizeof(JobBuffer), &cbData, &cJobs ); JobInfo = (PJOB_INFO_2) JobBuffer; if (!Result || cJobs == 0) { break; } // only consider the jobs that are paused if (!(JobInfo->Status & JOB_STATUS_PAUSED) || JobInfo->pParameters == NULL) { continue; } // if it is a send retry that has maxed out, delete the job RetryTag = ExtractFaxTag(FAXTAG_SEND_RETRY, JobInfo->pParameters, &cBytes); if (RetryTag) { Retries = _ttoi( RetryTag ); if (Retries == 0) { SystemTimeToFileTime( &JobInfo->Submitted, (FILETIME *) &SubmitTime); if (SubmitTime.QuadPart + (FaxDirtyDays * FILETIMETICKS_PER_DAY) < CurrentTime.QuadPart) { while (SetPrintJobCompleted( FaxPrinterInfo[i].hPrinter, JobInfo->JobId )) ; } } continue; } // if it is an inbound routing failure, try to route it again RouteTag = ExtractFaxTag(FAXTAG_ROUTE_FILE, JobInfo->pParameters, &cBytes); if (RouteTag) { PROUTE_INFO RouteInfo; RouteTag++; // skip over the space RouteInfo = LoadRouteInfo( RouteTag ); if (RouteInfo != NULL) { __try { FaxRoute( &RouteInfo->FaxReceive, &RouteInfo->LineInfo, &RouteInfo->FaxStatus, NULL, 0, RouteInfo->ElapsedTime ); } __except (EXCEPTION_EXECUTE_HANDLER) { // if the file is corrupt and causes an exception, delete it and cancel the print job DebugPrint(( TEXT("Exception processing routing information file") )); DeleteFile( RouteTag ); } MemFree( RouteInfo ); if (GetFileAttributes( RouteTag ) == 0xffffffff) { if(!SetJob( FaxPrinterInfo[i].hPrinter, JobInfo->JobId, 0, NULL, JOB_CONTROL_CANCEL )) { DebugPrint(( TEXT("CleanDirtyQueues - SetJob failed - ec %d"), GetLastError() )); } } else { SetPrintJobStatus( FaxPrinterInfo[i].hPrinter, JobInfo->JobId, FPS_ROUTERETRY, NULL, -1 ); } } } } } #endif }