/************************************************************************* * Microsoft Windows NT * * * * Copyright(c) Microsoft Corp., 1994 * * * * Revision History: * * * * Jan. 24,94 Koti Created * * Jan. 29,96 JBallard Modified * * * * Description: * * * * This file contains functions for carrying out LPD printing * * * *************************************************************************/ #include "lpd.h" extern FILE * pErrFile; // Debug output log file. BOOL GetSpoolFileName( HANDLE hPrinter, PSOCKCONN pscConn, PCHAR *ppwchSpoolPath ); VOID CleanupConn( PSOCKCONN pscConn); /***************************************************************************** * * * ProcessJob(): * * This function receives the subcommand from the client to expect the * * control file, then accepts the control file, then the subcommand to * * expect the data file, then accepts the data and then hands it over to * * the spooler to print. * * If the very first subcommand was to abort the job, we just return. * * * * Returns: * * Nothing * * * * Parameters: * * pscConn (IN-OUT): pointer to SOCKCONN structure for this connection * * * * History: * * Jan.24 94 Koti Created * * * *****************************************************************************/ VOID ProcessJob( PSOCKCONN pscConn ) { // the main functionality of LPD implemented in this function! CHAR chSubCmdCode; DWORD cbTotalDataLen; DWORD cbBytesSpooled; DWORD cbBytesToRead; DWORD cbDataBufLen; DWORD cbBytesRemaining; DWORD dwErrcode; CHAR chAck; HANDLE hDFile; PDFILE_ENTRY pDFile; PCHAR lpFileName; PCHAR pchDataBuf; DWORD ClientCmd; // initialize the printer that the client wants to use #ifdef DBG if( !pscConn || !pscConn->pchPrinterName || strstr( pscConn->pchPrinterName, "debug" ) ){ print__sockconn( "ProcessJob: entered", pscConn ); } #endif if ( InitializePrinter( pscConn ) != NO_ERROR ) { PCHAR aszStrings[2]; aszStrings[0] = pscConn->pchPrinterName; aszStrings[1] = pscConn->szIPAddr; LpdReportEvent( LPDLOG_NONEXISTENT_PRINTER, 2, aszStrings, 0 ); pscConn->fLogGenericEvent = FALSE; return; // fatal error: exit } // thank the client for the command. If we couldn't send, quit if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR ) { LPD_DEBUG( "ProcessJob(): couldn't ACK to \"receive job\"\n" ); return; } // 2 subcommands expected: "receive control file" and "receive data file" // They can come in any order. (One of the two subcommands can also be // "abort this job" in which case we abort the job and return). for ( ; ; ) { // don't need the previous one (in fact, pchCommand field is reused) if ( pscConn->pchCommand != NULL ) { LocalFree( pscConn->pchCommand ); pscConn->pchCommand = NULL; } // get the first subcommand from the client // ------------------------------ N = 02, 03, or 01 // | N | Count | SP | Name | LF | Count => control file length // ------------------------------ Name => controlfile name ClientCmd = GetCmdFromClient( pscConn ); switch ( ClientCmd ) { case CONNECTION_CLOSED: // performance enhancement: close the socket here and then start // printing: printing could take several seconds, // so don't tie up client if ( pscConn->sSock != INVALID_SOCKET ) { SureCloseSocket( pscConn->sSock ); pscConn->sSock = INVALID_SOCKET; } // if we came this far, everything went as planned. // tell spooler that we are done spooling: go ahead and print! PrintData( pscConn ); pscConn->wState = LPDS_ALL_WENT_WELL; return; case NO_ERROR: // Not yet done, back to outer loop. break; case SOCKET_ERROR: default: // If we didn't get a subcommand from client, it's bad news! // client died or something catastophic like that! LOGIT(("ProcessJob:GetCmdFromClient %d failed %d.\n", ClientCmd, GetLastError() )); return; // the thread exits without doing anything } chSubCmdCode = pscConn->pchCommand[0]; switch (chSubCmdCode) { case LPDCS_RECV_CFILE: // N = 02 ("receive control file") // client is going to give us a control file: prepare for it pscConn->wState = LPDSS_RECVD_CFILENAME; // get the controlfile name, file size out of the command if ( ParseSubCommand( pscConn, &cbTotalDataLen, &lpFileName ) != NO_ERROR ) { PCHAR aszStrings[2]={ pscConn->szIPAddr, NULL }; LpdReportEvent( LPDLOG_BAD_FORMAT, 1, aszStrings, 0 ); pscConn->fLogGenericEvent = FALSE; return; // fatal error: exit } // tell client we got the name of the controlfile ok if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR ) { return; // fatal error: exit } // Get the control file (we already know how big it is) if ( GetControlFileFromClient( pscConn, cbTotalDataLen, lpFileName ) != NO_ERROR ) { LPD_DEBUG( "GetControlFileFromClient() failed in ProcessJob()\n" ); return; } pscConn->wState = LPDSS_RECVD_CFILE; // tell client we got the controlfile and things look good so far! if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR ) { LOGIT(("ProcessJob:%d: ReplyToClient failed %d\n", __LINE__, GetLastError() )); return; // fatal error: exit } break; case LPDCS_RECV_DFILE: // N = 03 ("receive data file") pscConn->wState = LPDSS_RECVD_DFILENAME; // tell client we got the name of the datafile ok if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR ) { LOGIT(("ProcessJob:%d: ReplyToClient failed %d\n", __LINE__, GetLastError() )); return; // fatal error: exit } // get the datafile name, data size out of the command if ( ParseSubCommand( pscConn, &cbTotalDataLen, &lpFileName ) != NO_ERROR ) { PCHAR aszStrings[2]={ pscConn->szIPAddr, NULL }; LpdReportEvent( LPDLOG_BAD_FORMAT, 1, aszStrings, 0 ); pscConn->fLogGenericEvent = FALSE; LOGIT(("ProcessJob:%d: ParseSubCommand failed %d\n", __LINE__, GetLastError() )); return; // fatal error: exit } // at this point, we know exactly how much data is coming. // Allocate buffer to hold the data. If data is more than // LPD_BIGBUFSIZE, keep reading and spooling several times // over until data is done pscConn->wState = LPDSS_SPOOLING; pDFile = LocalAlloc( LMEM_FIXED, sizeof(DFILE_ENTRY) ); if (pDFile == NULL) { LocalFree( lpFileName ); LOGIT(("ProcessJob:%d: LocalAlloc failed %d\n", __LINE__, GetLastError() )); return; // Fatal Error } pDFile->cbDFileLen = cbTotalDataLen; pDFile->pchDFileName = lpFileName; if ( !GetSpoolFileName( pscConn->hPrinter, pscConn, &lpFileName ) ) { LPD_DEBUG( "ERROR: GetSpoolFileName() failed in ProcessJob\n" ); LocalFree( pDFile->pchDFileName ); LocalFree( pDFile ); return; } // // GetTempFileName has already created this file, so use OPEN_ALWAYS. // Also, use FILE_ATTRIBUTE_TEMPORARY so that it will be faster // FILE_FLAG_SEQUENTIAL_SCAN, ntbug 79854, MohsinA, 03-Jun-97. // pDFile->hDataFile = CreateFile( lpFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |FILE_ATTRIBUTE_TEMPORARY |FILE_FLAG_DELETE_ON_CLOSE |FILE_FLAG_SEQUENTIAL_SCAN , NULL ); LocalFree( lpFileName ); if ( pDFile->hDataFile == INVALID_HANDLE_VALUE ) { LPD_DEBUG( "ERROR: CreatFile() failed in ProcessJob \n" ); LocalFree( pDFile->pchDFileName ); LocalFree( pDFile ); return; } cbBytesToRead = (cbTotalDataLen > LPD_BIGBUFSIZE ) ? LPD_BIGBUFSIZE : cbTotalDataLen; pchDataBuf = LocalAlloc( LMEM_FIXED, cbBytesToRead ); if ( pchDataBuf == NULL ) { LOGIT(("ProcessJob:%d: LocalAlloc failed %d\n", __LINE__, GetLastError() )); CloseHandle(pDFile->hDataFile); pDFile->hDataFile = INVALID_HANDLE_VALUE; LocalFree( pDFile->pchDFileName ); LocalFree( pDFile ); return; // fatal error: exit } cbBytesSpooled = 0; cbBytesRemaining = cbTotalDataLen; // keep receiving until we have all the data client said it // would send while( cbBytesSpooled < cbTotalDataLen ) { if ( ReadData( pscConn->sSock, pchDataBuf, cbBytesToRead ) != NO_ERROR ) { LPD_DEBUG( "ProcessJob:ReadData failed, job aborted)\n" ); LocalFree( pchDataBuf ); CloseHandle(pDFile->hDataFile); pDFile->hDataFile = INVALID_HANDLE_VALUE; LocalFree( pDFile->pchDFileName ); LocalFree( pDFile ); return; // fatal error: exit } cbDataBufLen = cbBytesToRead; if ( SpoolData( pDFile->hDataFile, pchDataBuf, cbDataBufLen ) != NO_ERROR ) { LPD_DEBUG( "SpoolData() failed in ProcessJob(): job aborted)\n" ); LocalFree( pchDataBuf ); CloseHandle(pDFile->hDataFile); pDFile->hDataFile = INVALID_HANDLE_VALUE; LocalFree( pDFile->pchDFileName ); LocalFree( pDFile ); return; // fatal error: exit } cbBytesSpooled += cbBytesToRead; cbBytesRemaining -= cbBytesToRead; cbBytesToRead = (cbBytesRemaining > LPD_BIGBUFSIZE ) ? LPD_BIGBUFSIZE : cbBytesRemaining; } LocalFree( pchDataBuf ); InsertTailList( &pscConn->DFile_List, &pDFile->Link ); // LPR client sends one byte (of 0 bits) after sending data dwErrcode = ReadData( pscConn->sSock, &chAck, 1 ); if ( ( dwErrcode != NO_ERROR ) || (chAck != LPD_ACK ) ) { return; } // tell client we got the data and things look good so far! if ( ReplyToClient( pscConn, LPD_ACK ) != NO_ERROR ) { LOGIT(("ProcessJob:%d: ReplyToClient failed %d\n", __LINE__, GetLastError() )); return; // fatal error: exit } break; case LPDCS_ABORT_JOB: // N = 01 ("abort this job") // client asked us to abort the job: tell him "ok" and quit! ReplyToClient( pscConn, LPD_ACK ); pscConn->wState = LPDS_ALL_WENT_WELL; // we did what client wanted return; // unknown subcommand: log the event and quit default: { PCHAR aszStrings[2]={ pscConn->szIPAddr, NULL }; LpdReportEvent( LPDLOG_MISBEHAVED_CLIENT, 1, aszStrings, 0 ); pscConn->fLogGenericEvent = FALSE; LPD_DEBUG( "ProcessJob(): invalid subcommand, request rejected\n" ); return; } } } // done processing both subcommands } // end ProcessJob() /***************************************************************************** * * * GetControlFileFromClient(): * * This function receives the control file from the client. In the * * previsous subcommand, the client told us how many bytes there are in * * the control file. * * Also,after reading all the bytes, we read the 1 byte "ack" from client * * * * Returns: * * NO_ERROR if everything went well * * ErrorCode if something went wrong somewhere * * * * Parameters: * * pscConn (IN-OUT): pointer to SOCKCONN structure for this connection * * * * History: * * Jan.24, 94 Koti Created * * * *****************************************************************************/ DWORD GetControlFileFromClient( PSOCKCONN pscConn, DWORD FileSize, PCHAR FileName ) { PCHAR pchAllocBuf; DWORD cbBytesToRead; PCFILE_ENTRY pCFile; if (FileSize > LPD_MAX_CONTROL_FILE_LEN) { return( (DWORD)LPDERR_NOBUFS ); } pCFile = LocalAlloc( LMEM_FIXED, sizeof(CFILE_ENTRY) ); if (pCFile == NULL) { return( (DWORD)LPDERR_NOBUFS ); } pCFile->cbCFileLen = FileSize; pCFile->pchCFileName = FileName; // we know how big the control file is going to be: alloc space for it // Client sends one byte after sending the control file: read it along // with the rest of the data cbBytesToRead = FileSize + 1; pchAllocBuf = LocalAlloc( LMEM_FIXED, cbBytesToRead ); if (pchAllocBuf == NULL) { LocalFree( pCFile ); return( (DWORD)LPDERR_NOBUFS ); } // now read the data (and the trailing byte) into this allocated buffer if ( ReadData( pscConn->sSock, pchAllocBuf, cbBytesToRead ) != NO_ERROR ) { LocalFree( pCFile ); LocalFree( pchAllocBuf ); return( LPDERR_NORESPONSE ); } // if the trailing byte is not zero, treat it as job aborted (though // we don't expect this to happen really) if ( pchAllocBuf[cbBytesToRead-1] != 0 ) { LocalFree( pchAllocBuf ); LocalFree( pCFile ); LPD_DEBUG( "GetControlFileFromClient: got data followed by a NAK!\n"); return( LPDERR_JOBABORTED ); } pCFile->pchCFile = pchAllocBuf; InsertTailList( &pscConn->CFile_List, &pCFile->Link ); return( NO_ERROR ); } // end GetControlFileFromClient() /***************************************************************************** * * * GetSpoolFileName(): * * This function figures out where to put the spool file. * * * * Returns: * * TRUE if spool location found. * * FALSE if no spool location available. * * * * Parameters: * * hPrinter (IN): Handle to printer for which we are spooling * * pscConn (IN-OUT): pointer to SOCKCONN structure for this connection * * ppchSpoolPath (IN-OUT): Address of pointer which will receive the * * spool path. * * * * History: * * Nov.21, 94 JBallard * * * *****************************************************************************/ BOOL GetSpoolFileName ( HANDLE hPrinter, PSOCKCONN pscConn, PCHAR *ppchSpoolPath ) /*++ Routine Description: This function comes up with a name for a spool file that we should be able to write to. Note: The file name returned has already been created. Arguments: hPrinter - handle to the printer that we want a spool file for. ppchSpoolFileName: pointer that will receive an allocated buffer containing the file name to spool to. CALLER MUST FREE. Use LocalFree(). Return Value: TRUE if everything goes as expected. FALSE if anything goes wrong. --*/ { PBYTE pBuffer = NULL; DWORD dwAllocSize; DWORD dwNeeded; PCHAR pchSpoolPath = NULL; DWORD dwRetval; pchSpoolPath = LocalAlloc( LMEM_FIXED, 2 * MAX_PATH + 1 ); if ( pchSpoolPath == NULL ) { goto Failure; } // // In order to find out where the spooler's directory is, we add // call GetPrinterData with DefaultSpoolDirectory. // dwAllocSize = WCS_LEN( MAX_PATH + 1 ); for (;;) { pBuffer = LocalAlloc( LMEM_FIXED, dwAllocSize ); if ( pBuffer == NULL ) { goto Failure; } if ( GetPrinterData( hPrinter, SPLREG_DEFAULT_SPOOL_DIRECTORY, NULL, pBuffer, dwAllocSize, &dwNeeded ) == ERROR_SUCCESS ) { break; } if ( ( dwNeeded < dwAllocSize ) ||( GetLastError() != ERROR_MORE_DATA )) { goto Failure; } // // Free the current buffer and increase the size that we try to allocate // next time around. // LocalFree( pBuffer ); dwAllocSize = dwNeeded; } if( !GetTempFileName( (LPSTR)pBuffer, "LprSpl", 0, pchSpoolPath )) { goto Failure; } // // At this point, the spool file name should be done. Free the structure // we used to get the spooler temp dir and return. // LocalFree( pBuffer ); *ppchSpoolPath = pchSpoolPath; return( TRUE ); Failure: // // Clean up and fail. // if ( pBuffer != NULL ) { LocalFree( pBuffer ); } if ( pchSpoolPath != NULL ) { LocalFree( pchSpoolPath ); } return( FALSE ); } VOID CleanupCFile( PCFILE_ENTRY pCFile ) { if (pCFile->pchCFileName != NULL) { LocalFree( pCFile->pchCFileName ); pCFile->pchCFileName = NULL; } if (pCFile->pchCFile != NULL) { LocalFree( pCFile->pchCFile ); pCFile->pchCFile = NULL; } LocalFree( pCFile ); } VOID CleanupDFile( PDFILE_ENTRY pDFile ) { if (pDFile->pchDFileName != NULL) { LocalFree( pDFile->pchDFileName ); pDFile->pchDFileName = NULL; } if (pDFile->hDataFile != INVALID_HANDLE_VALUE) { CloseHandle(pDFile->hDataFile); } LocalFree( pDFile ); } VOID CleanupConn( PSOCKCONN pscConn) { LIST_ENTRY *pTmpList; CFILE_ENTRY *pCFile; DFILE_ENTRY *pDFile; while ( !IsListEmpty( &pscConn->CFile_List ) ) { pTmpList = RemoveHeadList( &pscConn->CFile_List ); pCFile = CONTAINING_RECORD( pTmpList, CFILE_ENTRY, Link ); CleanupCFile( pCFile ); } while ( !IsListEmpty( &pscConn->DFile_List ) ) { pTmpList = RemoveHeadList( &pscConn->DFile_List ); pDFile = CONTAINING_RECORD( pTmpList, DFILE_ENTRY, Link ); CleanupDFile( pDFile ); } return; }