windows-nt/Source/XPSP1/NT/printscan/print/spooler/inetsrv/spool.cxx
2020-09-26 16:20:57 +08:00

2290 lines
63 KiB
C++

/***************************************************************************
FILE spool.cpp
MODULE Printers ISAPI DLL
PURPOSE Spool Print Jobs
DESCRIBED IN
HISTORY 01/16/96 ccteng Stub
02/14/97 weihaic
11/11/97 sylvan IPP PrintJobRequest
11/20/97 chriswil Asynchronous read rewrite
****************************************************************************/
#include "pch.h"
#include "spool.h"
#include "printers.h"
#ifndef HSE_REQ_ASYNC_READ_CLIENT
#define HSE_REQ_ASYNC_READ_CLIENT ((DWORD)1010)
#endif
PINIJOB pIniFirstJob = NULL;
/*****************************************************************************
* EnterSplSem
* LeaveSplSem
*
*****************************************************************************/
#define EnterSplSem() EnterCriticalSection(&SplCritSect)
#define LeaveSplSem() LeaveCriticalSection(&SplCritSect)
/*****************************************************************************
* Spl_StrSize (Local Routine)
*
* Returns the size (in bytes) of the string (includes null-terminator).
*
*****************************************************************************/
inline DWORD Spl_StrSize(
LPCTSTR lpszStr)
{
return (lpszStr ? ((lstrlen(lpszStr) + 1) * sizeof(TCHAR)) : 0);
}
/*****************************************************************************
* Spl_CallSSF (Local Routine)
*
* Calls the ISAPI ServerSupportFunction
*
*****************************************************************************/
inline BOOL Spl_CallSSF(
LPEXTENSION_CONTROL_BLOCK pECB,
DWORD dwCmd,
LPVOID lpvBuf,
LPDWORD lpdwBuf,
LPDWORD lpdwType)
{
return pECB->ServerSupportFunction(pECB->ConnID, dwCmd, lpvBuf, lpdwBuf, lpdwType);
}
/*****************************************************************************
* Spl_SetAsyncCB (Local Routine)
*
* Calls the ISAPI ServerSupportFunction to set an asynchronous callback.
*
*****************************************************************************/
inline BOOL Spl_SetAsyncCB(
LPEXTENSION_CONTROL_BLOCK pECB,
LPVOID pfnCallback,
LPDWORD lpdwCtx)
{
return pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_IO_COMPLETION, pfnCallback, NULL, lpdwCtx);
}
/*****************************************************************************
* Spl_ReadClient (Local Routine)
*
* Calls the ISAPI ServerSupportFunction to do an asynchronous read.
*
*****************************************************************************/
inline BOOL Spl_ReadClient(
LPEXTENSION_CONTROL_BLOCK pECB,
LPVOID lpvBuf,
DWORD cbBuf)
{
DWORD dwType = HSE_IO_ASYNC;
return pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_ASYNC_READ_CLIENT, lpvBuf, &cbBuf, &dwType);
}
/*****************************************************************************
* Spl_WriteClient (Local Routine)
*
* Calls the ISAPI WriteClient to do a write.
*
*****************************************************************************/
inline BOOL Spl_WriteClient(
LPEXTENSION_CONTROL_BLOCK pECB,
LPVOID lpvBuf,
DWORD cbBuf)
{
return pECB->WriteClient(pECB->ConnID, lpvBuf, &cbBuf, (DWORD)NULL);
}
/*****************************************************************************
* Spl_EndSession (Local Routine)
*
* Calls the ISAPI ServerSupportFunction to end our session.
*
*****************************************************************************/
inline BOOL Spl_EndSession(
LPEXTENSION_CONTROL_BLOCK pECB)
{
DWORD dwStatus = HSE_STATUS_SUCCESS;
return pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_DONE_WITH_SESSION, &dwStatus, NULL, NULL);
}
/*****************************************************************************
* Spl_WriteJob (Local Routine)
*
* Writes the byte-stream for a print-job.
*
*****************************************************************************/
BOOL Spl_WriteJob(
DWORD dwJobId,
LPBYTE lpbData,
DWORD cbBytes)
{
DWORD cbLeft;
DWORD cbWritten;
BOOL fRet = TRUE;
for (cbLeft = cbBytes; (cbLeft > 0) && fRet; ) {
if (fRet = WriteJob(dwJobId, lpbData, cbBytes, &cbWritten)) {
lpbData += cbWritten;
cbLeft -= cbWritten;
} else {
DBGMSG(DBG_WARN, ("Spl_WriteJob() call failed.\r\n"));
break;
}
}
return fRet;
}
/*****************************************************************************
* Spl_AllocPrtUri (Local Routine)
*
* Returns a PrinterURI string.
*
*****************************************************************************/
LPTSTR Spl_AllocPrtUri(
LPCTSTR lpszShare,
LPDWORD lpcbUri,
BOOL bSecure)
{
DWORD cch;
DWORD cbSize;
LPTSTR lpszUri;
// Get the size necessary to hold the printer-uri.
//
*lpcbUri = 0;
cch = 0;
GetWebpnpUrl(g_szHttpServerName, lpszShare, NULL, bSecure, NULL, &cch);
if (cch && (lpszUri = (LPTSTR)AllocSplMem(sizeof(TCHAR) * cch))) {
if (GetWebpnpUrl(g_szHttpServerName, lpszShare, NULL, bSecure, lpszUri, &cch)) {
*lpcbUri = cch * sizeof(TCHAR);
return lpszUri;
}
FreeSplMem(lpszUri, cch * sizeof(TCHAR));
}
return NULL;
}
/*****************************************************************************
* Spl_AllocJobUri (Local Routine)
*
* Returns a JobURI string.
*
*****************************************************************************/
LPTSTR Spl_AllocJobUri(
LPCTSTR lpszShare,
DWORD idJob,
LPDWORD lpcbUri,
BOOL bBase,
BOOL bSecure)
{
LPTSTR lpszPrt;
DWORD cbSize;
DWORD cbPrt;
DWORD cch;
LPTSTR lpszUri = NULL;
static CONST TCHAR s_szFmt1[] = TEXT("%s?IPP&JobId=%d");
static CONST TCHAR s_szFmt2[] = TEXT("%s?IPP&JobId=");
// Set our return-count to zero.
//
*lpcbUri = 0;
// Get the printer-uri, and append a job-id to the end
// as our job-uri.
//
cbPrt = 0;
if (lpszPrt = Spl_AllocPrtUri(lpszShare, &cbPrt, bSecure)) {
cbSize = cbPrt + sizeof(s_szFmt1) + 40;
if (lpszUri = (LPTSTR)AllocSplMem(cbSize)) {
if (bBase)
cch = wsprintf(lpszUri, s_szFmt2, lpszPrt);
else
cch = wsprintf(lpszUri, s_szFmt1, lpszPrt, idJob);
// *lpcbUri = (cch * sizeof(TCHAR)); This is the number of bytes written, not
// the amount of memory allocated
*lpcbUri = cbSize;
}
FreeSplMem(lpszPrt, cbPrt);
}
return lpszUri;
}
/*****************************************************************************
* Spl_GetJI2 (Local Routine)
*
* Returns a JOB_INFO_2 struct.
*
*****************************************************************************/
LPJOB_INFO_2 Spl_GetJI2(
HANDLE hPrinter,
DWORD idJob,
LPDWORD lpcbSize)
{
DWORD cbSize;
DWORD dwLE;
LPJOB_INFO_2 pji2 = NULL;
// Clear return-size.
//
*lpcbSize = 0;
// Get the size necessary for the job.
//
cbSize = 0;
GetJob(hPrinter, idJob, 2, NULL, 0, &cbSize);
// Get the job-information.
//
if (cbSize && (pji2 = (LPJOB_INFO_2)AllocSplMem(cbSize))) {
if (GetJob(hPrinter, idJob, 2, (LPBYTE)pji2, cbSize, &cbSize)) {
*lpcbSize = cbSize;
} else {
dwLE = GetLastError();
FreeSplMem(pji2, cbSize);
pji2 = NULL;
}
} else {
dwLE = GetLastError();
}
if (pji2 == NULL)
SetLastError(dwLE);
return pji2;
}
/*****************************************************************************
* Spl_AllocAsync
*
* Allocate a spool-async-read structure. This is basically a structure
* that we use to track where we are in the asynchronous read processing.
*
* Parameter/Field descriptions
* ----------------------------
* wReq - IPP Request identifier.
*
* hPrinter - handle to printer. We are in charge of closing this when
* we're done processing the asynchronous reads.
*
* lpszShare - share-name of printer. This is necessary when we respond
* back to the client when done processing the job.
*
* cbTotal - Total number of bytes to expect in job.
*
* cbRead - Current bytes read during async read.
*
* cbBuf - Size of read-buffer.
*
* lpbRet - Return-Buffer dependent upon IPP Request identifier.
*
*****************************************************************************/
LPSPLASYNC Spl_AllocAsync(
WORD wReq,
HANDLE hPrinter,
LPCTSTR lpszShare,
DWORD cbTotal)
{
LPSPLASYNC pAsync;
if (pAsync = (LPSPLASYNC)AllocSplMem(sizeof(SPLASYNC))) {
if (pAsync->hIpp = WebIppRcvOpen(wReq)) {
if (pAsync->lpbBuf = (LPBYTE)AllocSplMem(SPL_ASYNC_BUF)) {
if (pAsync->lpszShare = AllocSplStr(lpszShare)) {
pAsync->wReq = wReq;
pAsync->hPrinter = hPrinter;
pAsync->cbTotal = cbTotal;
pAsync->cbRead = 0;
pAsync->cbBuf = SPL_ASYNC_BUF;
pAsync->lpbRet = NULL;
return pAsync;
}
FreeSplMem(pAsync->lpbBuf, SPL_ASYNC_BUF);
}
WebIppRcvClose(pAsync->hIpp);
}
FreeSplMem(pAsync, sizeof(SPLASYNC));
}
DBGMSG(DBG_ERROR, ("Spl_AllocAsync() : Out of Memory\r\n"));
SetLastError(ERROR_OUTOFMEMORY);
return NULL;
}
/*****************************************************************************
* Spl_FreeAsync
*
* Free our asynchronous read structure. This also closes our printer
* handle that was setup prior to the beginning of the job.
*
*****************************************************************************/
BOOL Spl_FreeAsync(
LPSPLASYNC pAsync)
{
LPIPPRET_JOB pj;
// Close the printer-handle. We do this here as oppose to in
// (msw3prt.cxx), since if we had performed asynchronous reads
// we would need to leave the scope the HttpExtensionProc() call.
//
// NOTE: CloseJob() closes the printer-handle. Only in the case
// where we were not able to open a job should we close it
// here.
//
pj = (LPIPPRET_JOB)pAsync->lpbRet;
if ((pAsync->wReq == IPP_REQ_PRINTJOB) && pj && pj->bRet) {
CloseJob((DWORD)pj->bRet);
} else {
ClosePrinter(pAsync->hPrinter);
}
// Free up our Ipp-handle, and all resources allocated.
//
if (pAsync->lpbBuf)
FreeSplMem(pAsync->lpbBuf, pAsync->cbBuf);
if (pAsync->lpszShare)
FreeSplStr(pAsync->lpszShare);
if (pAsync->lpbRet)
WebIppFreeMem(pAsync->lpbRet);
if (pAsync->hIpp)
WebIppRcvClose(pAsync->hIpp);
FreeSplMem(pAsync, sizeof(SPLASYNC));
return TRUE;
}
/*****************************************************************************
* Spl_OpenPrn (Local Routine)
*
* Opens a printer-handle with administrator rights.
*
*****************************************************************************/
HANDLE Spl_OpenPrn(
HANDLE hPrinter)
{
PPRINTER_INFO_1 ppi;
PRINTER_DEFAULTS pa;
DWORD cbSize;
HANDLE hPrn = NULL;
cbSize = 0;
GetPrinter(hPrinter, 1, NULL, 0, &cbSize);
if (cbSize && (ppi = (PPRINTER_INFO_1)AllocSplMem(cbSize))) {
if (GetPrinter(hPrinter, 1, (LPBYTE)ppi, cbSize, &cbSize)) {
// Since the OpenPrinter call in msw3prt.cxx has been
// opened with the share-name, the (pName) field of this
// call will already have the server-name prepended to the
// friendly-name. We do not need to do any further work
// on the friendly-name to accomodate clustering. If in the
// future the OpenPrinter() specifies the friendly-name over
// the share-name, then this routine will need to call
// genFrnName() to convert the friendly to <server>\friendly.
//
ZeroMemory(&pa, sizeof(PRINTER_DEFAULTS));
pa.DesiredAccess = PRINTER_ALL_ACCESS;
OpenPrinter(ppi->pName, &hPrn, &pa);
}
FreeSplMem(ppi, cbSize);
}
return hPrn;
}
/*****************************************************************************
** Spl_AllocSplMemFn (Local Routine)
**
** The WebIppPackJI2 call takes an allocator, however AllocSplMem is a #define
** if we are not using a debug library, so in this case, we have to create
** a small stub function ourselves.
**
*****************************************************************************/
#ifdef DEBUG
#define Spl_AllocSplMemFn AllocSplMem
#else
LPVOID Spl_AllocSplMemFn(DWORD cb) {
return LocalAlloc(LPTR, cb);
}
#endif // #ifdef DEBUG
/*****************************************************************************
* Spl_CreateJobInfo2 (Local Routine)
*
* This creates a JobInfo2 structure from the various printer details that have
* been passed in to us.
*
*****************************************************************************/
LPJOB_INFO_2 Spl_CreateJobInfo2(
IN PIPPREQ_PRTJOB ppj, // This provides some info that is useful for constructing
// our own JOB_INFO_2 if necessary
IN PINIJOB pInijob, // This is also used for constructing a JOB_INFO_2
OUT LPDWORD lpcbSize
) {
ASSERT(ppj); // This should never be NULL if this code path is reached
ASSERT(lpcbSize);
ASSERT(*lpcbSize == 0); // This should be passed in zero
LPJOB_INFO_2 pji2 = NULL; // The packed and allocated JI2
if (pInijob) { // This could conceivably be NULL
DWORD cbNeeded = 0;
GetPrinter( pInijob->hPrinter, 2, NULL, 0, &cbNeeded );
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && cbNeeded) {
LPPRINTER_INFO_2 ppi2; // The printer info to fill out
DWORD cbNextNeeded;
ppi2 = (LPPRINTER_INFO_2)AllocSplMem( cbNeeded );
if (ppi2 && GetPrinter( pInijob->hPrinter, 2, (LPBYTE)ppi2, cbNeeded, &cbNextNeeded) ) {
JOB_INFO_2 ji2; // The ji2 we will fill out, a properly packed one will
// be returned
ZeroMemory( &ji2, sizeof(ji2) );
ji2.JobId = pInijob->JobId;
ji2.pPrinterName = ppi2->pPrinterName;
ji2.pMachineName = ppi2->pServerName;
ji2.pUserName = ppj->pUserName;
ji2.pNotifyName = ppj->pUserName;
ji2.pDocument = ppj->pDocument;
ji2.pDatatype = ppi2->pDatatype;
ji2.pPrintProcessor = ppi2->pPrintProcessor;
ji2.pParameters = ppi2->pParameters;
ji2.pDriverName = ppi2->pDriverName;
ji2.Status = pInijob->dwStatus;
ji2.Priority = ppi2->Priority;
ji2.StartTime = pInijob->dwStartTime;
ji2.UntilTime = pInijob->dwStartTime;
GetSystemTime (&ji2.Submitted);
pji2 = WebIppPackJI2(&ji2, lpcbSize, Spl_AllocSplMemFn);
}
if (ppi2)
FreeSplMem( ppi2, cbNeeded );
}
}
return pji2;
}
/*****************************************************************************
* Spl_ConfirmJob (Local Routine)
*
* This confirms that a Job has been printed if it returns a JOB_INFO_2 if
* this is possible. GSNW printers can return an ERROR_PRINT_CANCELLED for the
* first GetJob call and then fail on the second call. In this case we need to
* build a shell JOB_INFO_2 with whatever data we can find and return it. We pass
* this in to Web FILL IN HERE which packs the correct strings and returns a
* Legitimate JOB_INFO_2 structure.
*
*****************************************************************************/
LPJOB_INFO_2 Spl_ConfirmJob(
IN HANDLE hPrinter, // This is the printer handle we are using
IN DWORD idJob, // This is the job id given to us by StartDocPrinter
OUT LPDWORD lpcbSize, // This is the size of the allocated block
IN PIPPREQ_PRTJOB ppj, // This provides some info that is useful for constructing
// our own JOB_INFO_2 if necessary
IN PINIJOB pInijob // This is also used for constructing a JOB_INFO_2
)
{
DWORD cbSize;
DWORD dwLE;
LPJOB_INFO_2 pji2 = NULL;
ASSERT(lpcbSize);
// Clear return-size.
//
*lpcbSize = 0;
// Get the size necessary for the job.
//
cbSize = 0;
GetJob(hPrinter, idJob, 2, NULL, 0, &cbSize);
dwLE = GetLastError();
switch(dwLE) {
case ERROR_INSUFFICIENT_BUFFER:
// Get the job-information.
//
if (cbSize && (pji2 = (LPJOB_INFO_2)AllocSplMem(cbSize))) {
if (GetJob(hPrinter, idJob, 2, (LPBYTE)pji2, cbSize, &cbSize)) {
*lpcbSize = cbSize;
} else {
dwLE = GetLastError();
FreeSplMem(pji2, cbSize);
pji2 = NULL;
}
} else {
dwLE = GetLastError();
}
break;
case ERROR_PRINT_CANCELLED:
// This is special-cased for GSNW masq printers where the job cannot
// be retrieved from the Server, but we do not want to fail the EndDocPrinter
// call
if (pji2 = Spl_CreateJobInfo2( ppj, pInijob, lpcbSize) )
SetLastError(dwLE = ERROR_SUCCESS);
break;
case ERROR_SUCCESS:
// What the?
dwLE = ERROR_INVALID_PARAMETER;
break;
}
if (pji2 == NULL)
SetLastError(dwLE);
return pji2;
}
/*****************************************************************************
* Spl_IppJobDataPrt (Local Routine)
*
* Handles the IPP_REQ_PRINTJOB request.
*
*****************************************************************************/
BOOL Spl_IppJobDataPrt(
LPEXTENSION_CONTROL_BLOCK pECB,
HANDLE hPrinter,
LPCTSTR lpszShare,
LPBYTE lpbHdr,
DWORD cbHdr,
LPBYTE lpbDta,
DWORD cbDta,
LPBYTE* lplpbRet)
{
PIPPREQ_PRTJOB ppj;
JOB_INFO_IPP ipp;
DWORD cbUri;
DWORD cbPrn;
DWORD cbJI2;
WORD wError;
DWORD idJob = 0;
LPJOB_INFO_2 pji2 = NULL;
BOOL bRet = FALSE;
if (ppj = (PIPPREQ_PRTJOB)lpbHdr) {
// Initialize job-information.
//
ZeroMemory(&ipp, sizeof(JOB_INFO_IPP));
// See if we're only to validate the job.
//
if (ppj->bValidate) {
// NOTE: We'll return only a success for now until
// we can build a table of validation criteria.
//
// 30-Jul-1998 : ChrisWil
//
wError = IPPRSP_SUCCESS;
idJob = (DWORD)TRUE;
} else {
// Start the job.
//
PINIJOB pInijob;
if (idJob = OpenJob(pECB, hPrinter, ppj, cbHdr, &pInijob)) {
if (pji2 = Spl_ConfirmJob(hPrinter, idJob, &cbJI2, ppj, pInijob)) {
wError = IPPRSP_SUCCESS;
ipp.pJobUri = Spl_AllocJobUri(lpszShare, idJob, &cbUri, FALSE, IsSecureReq(pECB));
ipp.pPrnUri = Spl_AllocPrtUri(lpszShare, &cbPrn, IsSecureReq(pECB));
} else {
wError = WebIppLeToRsp(GetLastError());
}
// Delete the pIniJob (if it has been allocated)
if (pInijob)
FreeSplMem( pInijob, sizeof(INIJOB) );
} else {
wError = WebIppLeToRsp(GetLastError());
}
}
// Build the return structure.
//
*lplpbRet = (LPBYTE)WebIppCreateJobRet(wError,
(BOOL)idJob,
ppj->bValidate,
pji2,
&ipp);
// Free allocated resources.
//
WebIppFreeMem(lpbHdr);
if (pji2)
FreeSplMem(pji2, cbJI2);
if (ipp.pJobUri)
FreeSplMem(ipp.pJobUri, cbUri);
if (ipp.pPrnUri)
FreeSplMem(ipp.pPrnUri, cbPrn);
// If we failed to get a job-id, then we need to
// return with no further processing.
//
if (idJob == 0)
return FALSE;
bRet = TRUE;
} else {
// If we had no header, then we are processing stream data
// for the job. In this case we would have already been through
// the code-path above where the job-id was set as our return
// code.
//
if (*lplpbRet)
idJob = (DWORD)((PIPPRET_JOB)*lplpbRet)->bRet;
}
// If we were able to get a data-stream, then we
// need to process that in the write. If we are chunking
// data and the lpbHdr is NULL, then the (lpdwJobId) is
// passed in as input to this routine to be used in chunk
// writes.
//
if (lpbDta)
bRet = Spl_WriteJob(idJob, lpbDta, cbDta);
return bRet;
}
/*****************************************************************************
* Spl_IppJobDataSet (Local Routine)
*
* Handles the SetJob requests.
*
*****************************************************************************/
BOOL Spl_IppJobDataSet(
LPEXTENSION_CONTROL_BLOCK pECB,
HANDLE hPrinter,
LPBYTE lpbHdr,
DWORD cbHdr,
LPBYTE* lplpbRet)
{
PIPPREQ_SETJOB psj;
JOB_INFO_2 ji2;
WORD wError;
BOOL bRet = FALSE;
if (psj = (PIPPREQ_SETJOB)lpbHdr) {
// Initialize job-information.
//
ZeroMemory(&ji2, sizeof(JOB_INFO_2));
// Perform the SetJob command.
//
bRet = SetJob(hPrinter, psj->idJob, 0, NULL, psj->dwCmd);
// Get LastError for return to the client.
//
wError = (bRet ? IPPRSP_SUCCESS : WebIppLeToRsp(GetLastError()));
// Return the SetJobRet structure.
//
*lplpbRet = (LPBYTE)WebIppCreateJobRet(wError, bRet, FALSE, &ji2, NULL);
// Free allocated resources.
//
WebIppFreeMem(lpbHdr);
}
return bRet;
}
/*****************************************************************************
* Spl_IppJobDataAth (Local Routine)
*
* Handles the Authentication request.
*
*****************************************************************************/
BOOL Spl_IppJobDataAth(
LPEXTENSION_CONTROL_BLOCK pECB,
LPBYTE lpbHdr,
DWORD cbHdr,
LPBYTE* lplpbRet)
{
PIPPREQ_AUTH pfa;
WORD wError;
BOOL bRet = FALSE;
if (pfa = (PIPPREQ_AUTH)lpbHdr) {
// Call authentication check.
//
bRet = !IsUserAnonymous(pECB);
// Get LastError for return to the client.
//
wError = (bRet ? IPPRSP_SUCCESS : IPPRSP_ERROR_401);
// Return the SetJobRet structure.
//
*lplpbRet = (LPBYTE)WebIppCreateAuthRet(wError, bRet);
// Free allocated resources.
//
WebIppFreeMem(lpbHdr);
}
return bRet;
}
/*****************************************************************************
* Spl_IppJobDataEnu (Local Routine)
*
* Handles the IPP_REQ_ENUJOB request. This returns a complete enumeration
* of jobs. It is up to the client to determine which job they are
* interested in (if they're only interested in one-job).
*
*****************************************************************************/
BOOL Spl_IppJobDataEnu(
LPEXTENSION_CONTROL_BLOCK pECB,
HANDLE hPrinter,
LPCTSTR lpszShare,
LPBYTE lpbHdr,
DWORD cbHdr,
LPBYTE* lplpbRet)
{
LPIPPREQ_ENUJOB pgj;
LPIPPJI2 lpIppJi2;
LPTSTR lpszJobBase;
LPJOB_INFO_2 lpji2;
DWORD cbJobs;
DWORD cJobs;
DWORD cbNeed;
DWORD cNeed;
DWORD cbUri;
WORD wError;
BOOL bRet = FALSE;
if (pgj = (LPIPPREQ_ENUJOB)lpbHdr) {
// Initialize IPP return variables.
//
cbJobs = 0;
cJobs = 0;
lpji2 = NULL;
// Get the size necessary to hold the enumerated jobs. We
// will return JOB_INFO_2, since that has the most information.
//
cbNeed = 0;
bRet = EnumJobs(hPrinter, 0, pgj->cJobs, 2, NULL, 0, &cbNeed, &cNeed);
// If we have jobs to enumerate, then grab them.
//
if (cbNeed && (lpji2 = (LPJOB_INFO_2)AllocSplMem(cbNeed))) {
bRet = EnumJobs(hPrinter,
0,
pgj->cJobs,
2,
(LPBYTE)lpji2,
cbNeed,
&cbJobs,
&cJobs);
DBGMSG(DBG_INFO,("Spl_IppJobDataEnu(): cJobs(%d), cbJobs(%d)\r\n", cJobs, cbJobs));
}
wError = (bRet ? IPPRSP_SUCCESS : WebIppLeToRsp(GetLastError()));
// Convert the enumerated-jobs to an IPPJI2 structure. This
// allows us to pass information that is not part of a JOB_INFO_2.
//
lpszJobBase = Spl_AllocJobUri(lpszShare, 0, &cbUri, TRUE, IsSecureReq (pECB));
lpIppJi2 = WebIppCvtJI2toIPPJI2(lpszJobBase, &cbJobs, cJobs, lpji2);
if (lpszJobBase)
FreeSplMem(lpszJobBase, cbUri);
// Return the EnuJobRet structure as an IPP stream.
//
*lplpbRet = (LPBYTE)WebIppCreateEnuJobRet(wError,
bRet,
cbJobs,
cJobs,
lpIppJi2);
// Free allocated resources.
//
WebIppFreeMem(lpbHdr);
if (lpIppJi2)
WebIppFreeMem(lpIppJi2);
if (lpji2)
FreeSplMem(lpji2, cbNeed);
}
return bRet;
}
/*****************************************************************************
* Spl_IppJobDataGet (Local Routine)
*
* Handles the IPP_REQ_GETJOB request. This returns the information for a single
* job.
*
*****************************************************************************/
BOOL Spl_IppJobDataGet(
LPEXTENSION_CONTROL_BLOCK pECB,
HANDLE hPrinter,
LPCTSTR lpszShare,
LPBYTE lpbHdr,
DWORD cbHdr,
LPBYTE* lplpbRet)
{
PIPPREQ_GETJOB pgj;
LPJOB_INFO_2 pji2;
JOB_INFO_IPP ipp;
DWORD cbUri;
DWORD cbPrn;
WORD wError;
DWORD cbJI2;
BOOL bRet = FALSE;
if (pgj = (PIPPREQ_GETJOB)lpbHdr) {
// Initialize job-information.
//
ZeroMemory(&ipp, sizeof(JOB_INFO_IPP));
if (pji2 = Spl_GetJI2(hPrinter, pgj->idJob, &cbJI2)) {
wError = IPPRSP_SUCCESS;
ipp.pJobUri = Spl_AllocJobUri(lpszShare, pgj->idJob, &cbUri, FALSE, IsSecureReq(pECB));
ipp.pPrnUri = Spl_AllocPrtUri(lpszShare, &cbPrn, IsSecureReq(pECB));
} else {
wError = WebIppLeToRsp(GetLastError());
}
// Set the return value.
//
*lplpbRet = (LPBYTE)WebIppCreateJobRet(wError, bRet, FALSE, pji2, &ipp);
// Free allocated resources.
//
WebIppFreeMem(lpbHdr);
if (pji2)
FreeSplMem(pji2, cbJI2);
if (ipp.pJobUri)
FreeSplMem(ipp.pJobUri, cbUri);
if (ipp.pPrnUri)
FreeSplMem(ipp.pPrnUri, cbPrn);
}
return bRet;
}
/*****************************************************************************
* Spl_IppPrnDataGet (Local Routine)
*
* Handles the IPP_REQ_GETPRN request.
*
*****************************************************************************/
BOOL Spl_IppPrnDataGet(
LPEXTENSION_CONTROL_BLOCK pECB,
HANDLE hPrinter,
LPCTSTR lpszShare,
LPBYTE lpbHdr,
DWORD cbHdr,
LPBYTE* lplpbRet)
{
LPIPPREQ_GETPRN pgp;
LPPRINTER_INFO_2 lppi2;
PRINTER_INFO_IPP ipp;
DWORD cbSize;
DWORD cbUri;
WORD wError;
BOOL bRet = FALSE;
if (pgp = (LPIPPREQ_GETPRN)lpbHdr) {
// Initialize the default information.
//
ZeroMemory(&ipp, sizeof(PRINTER_INFO_IPP));
ipp.pPrnUri = Spl_AllocPrtUri(lpszShare, &cbUri, IsSecureReq(pECB));
// Get PRINTER_INFO_2 information.
//
cbSize = 0;
GetPrinter(hPrinter, 2, NULL, 0, &cbSize);
if (lppi2 = (LPPRINTER_INFO_2)AllocSplMem(cbSize)) {
bRet = GetPrinter(hPrinter, 2, (LPBYTE)lppi2, cbSize, &cbSize);
if (!bRet) { // lppi2 might be full of garbage, so free it and pass NULL
FreeSplMem( lppi2, cbSize );
lppi2 = NULL;
}
}
// Grab last-error if call failed.
//
wError = (bRet ? IPPRSP_SUCCESS : WebIppLeToRsp(GetLastError()));
// Return the printer-structure.
//
*lplpbRet = (LPBYTE)WebIppCreatePrnRet(wError, bRet, lppi2, &ipp);
// Free allocated resources.
//
if (lppi2)
FreeSplMem(lppi2, cbSize);
if (ipp.pPrnUri)
FreeSplMem(ipp.pPrnUri, cbUri);
WebIppFreeMem(lpbHdr);
}
return bRet;
}
/*****************************************************************************
* Spl_IppPrnDataSet (Local Routine)
*
* Handles SetPrinter Requests.
*
*****************************************************************************/
BOOL Spl_IppPrnDataSet(
LPEXTENSION_CONTROL_BLOCK pECB,
HANDLE hPrinter,
LPBYTE lpbHdr,
DWORD cbHdr,
LPBYTE* lplpbRet)
{
PIPPREQ_SETPRN psp;
PRINTER_INFO_2 pi2;
HANDLE hPrn;
WORD wError;
BOOL bRet = FALSE;
if (psp = (PIPPREQ_SETPRN)lpbHdr) {
// Initialize default information.
//
ZeroMemory(&pi2, sizeof(PRINTER_INFO_2));
// Open the printer with admin-priviledges to get
// the printer information.
//
if (hPrn = Spl_OpenPrn(hPrinter)) {
// Set the job for SetPrinter.
//
if ((bRet = SetPrinter(hPrn, 0, NULL, psp->dwCmd)) == FALSE)
wError = WebIppLeToRsp(GetLastError());
else
wError = IPPRSP_SUCCESS;
ClosePrinter(hPrn);
} else {
wError = WebIppLeToRsp(GetLastError());
}
// Return the printer-information structure.
//
*lplpbRet = (LPBYTE)WebIppCreatePrnRet(wError, bRet, &pi2, NULL);
// Free allocated resources.
//
WebIppFreeMem(lpbHdr);
}
return bRet;
}
/*****************************************************************************
* Spl_IppJobData (Local Routine)
*
* Processes ipp stream data. This returns a structure specific to the
* type of request.
*
*****************************************************************************/
BOOL Spl_IppJobData(
LPEXTENSION_CONTROL_BLOCK pECB,
WORD wReq,
HANDLE hPrinter,
LPCTSTR lpszShare,
HANDLE hIpp,
LPBYTE lpbBuf,
DWORD cbBuf,
LPBYTE* lplpbRet)
{
DWORD dwIpp;
LPBYTE lpbHdr;
DWORD cbHdr;
LPBYTE lpbDta;
DWORD cbDta;
BOOL bRet = FALSE;
// Convert the stream.
//
dwIpp = WebIppRcvData(hIpp, lpbBuf, cbBuf, &lpbHdr, &cbHdr, &lpbDta, &cbDta);
// See how to process it.
//
switch (dwIpp) {
case WEBIPP_OK:
switch (wReq) {
case IPP_REQ_FORCEAUTH:
bRet = Spl_IppJobDataAth(pECB, lpbHdr, cbHdr, lplpbRet);
break;
case IPP_REQ_PRINTJOB:
case IPP_REQ_VALIDATEJOB:
bRet = Spl_IppJobDataPrt(pECB,
hPrinter,
lpszShare,
lpbHdr,
cbHdr,
lpbDta,
cbDta,
lplpbRet);
break;
case IPP_REQ_CANCELJOB:
case IPP_REQ_PAUSEJOB:
case IPP_REQ_RESUMEJOB:
case IPP_REQ_RESTARTJOB:
bRet = Spl_IppJobDataSet(pECB, hPrinter, lpbHdr, cbHdr, lplpbRet);
break;
case IPP_REQ_ENUJOB:
bRet = Spl_IppJobDataEnu(pECB, hPrinter, lpszShare, lpbHdr, cbHdr, lplpbRet);
break;
case IPP_REQ_GETJOB:
bRet = Spl_IppJobDataGet(pECB, hPrinter, lpszShare, lpbHdr, cbHdr, lplpbRet);
break;
case IPP_REQ_GETPRN:
bRet = Spl_IppPrnDataGet(pECB, hPrinter, lpszShare, lpbHdr, cbHdr, lplpbRet);
break;
case IPP_REQ_PAUSEPRN:
case IPP_REQ_RESUMEPRN:
case IPP_REQ_CANCELPRN:
bRet = Spl_IppPrnDataSet(pECB, hPrinter, lpbHdr, cbHdr, lplpbRet);
break;
}
break;
case WEBIPP_MOREDATA:
// More processing. Do nothing here.
//
*lplpbRet = NULL;
bRet = TRUE;
break;
case WEBIPP_NOMEMORY:
DBGMSG(DBG_WARN, ("Spl_IppJobData() failed (%d)\r\n", dwIpp));
*lplpbRet = NULL;
bRet = FALSE;
break;
case WEBIPP_BADHANDLE:
*lplpbRet = (LPBYTE)WebIppCreateBadRet(IPPRSP_ERROR_500, FALSE);
bRet = FALSE;
break;
default:
case WEBIPP_FAIL:
*lplpbRet = (LPBYTE)WebIppCreateBadRet(WebIppGetError(hIpp), FALSE);
bRet = TRUE;
break;
}
return bRet;
}
/*****************************************************************************
* Spl_IppJobRsp
*
* Sends back a job-response in IPP format.
*
*****************************************************************************/
BOOL Spl_IppJobRsp(
LPEXTENSION_CONTROL_BLOCK pECB,
WORD wReq,
LPREQINFO lpri,
LPBYTE lpbRet)
{
LPBYTE lpIpp;
DWORD cbIpp;
DWORD cbHdr;
DWORD dwIpp;
DWORD cch;
LPCSTR lpszErr;
CHAR szHdr[1024];
BOOL bRet = FALSE;
static CONST CHAR s_szErr400[] = "400 Failed Response";
static CONST CHAR s_szErr401[] = "401 Authentication Required";
static CONST CHAR s_szHtpHdr[] = "Content-Type: application/ipp\r\nContent-Length: %d\r\n\r\n";
if (lpbRet) {
// Convert to an IPP-Buffer from the return-buffer structure. For
// failure cases, the last-error is initialized in the (lpbRet)
// structure so that the appropriate stream can be generated.
//
dwIpp = WebIppSndData((IPP_RESPONSE | wReq),
lpri,
lpbRet,
*((LPDWORD)lpbRet),
&lpIpp,
&cbIpp);
if (dwIpp == WEBIPP_OK) {
// If we had an access-denied, then we will need to include
// error 401. This will force the client to prompt for
// validation.
//
if (((LPIPPRET_ALL)lpbRet)->dwLastError == ERROR_ACCESS_DENIED)
lpszErr = s_szErr401;
else
lpszErr = NULL;
// Build header information.
//
cch = wsprintfA(szHdr, s_szHtpHdr, cbIpp);
// First we send a standard SEND_RESPONSE_HEADER w/our
// content-type ServerSupportFunction only handles szText,
// ANSI ??? see URL:
//
// http://www.microsoft.com/WIN32DEV/APIEXT/ISAPIREF.HTM
//
// see include httpfilt.h
//
Spl_CallSSF(pECB,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPVOID)lpszErr,
(LPDWORD)&cch,
(LPDWORD)szHdr);
// For binary data we use WriteClient.
//
bRet = Spl_WriteClient(pECB, lpIpp, cbIpp);
WebIppFreeMem(lpIpp);
} else {
DBGMSG(DBG_WARN, ("Warn: WebIppSndData failed (%d)", dwIpp));
}
}
// Send an HTTP error header if we had big problems...
//
if (bRet == FALSE) {
cch = lstrlenA(s_szErr400);
Spl_CallSSF(pECB,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPVOID)s_szErr400,
(LPDWORD)&cch,
(LPDWORD)NULL);
}
return bRet;
}
/*****************************************************************************
* Spl_IppJobAsyncCB
*
* Process the asynchronous reads. This is called by a random ISAPI thread.
*
*****************************************************************************/
VOID Spl_IppJobAsyncCB(
LPEXTENSION_CONTROL_BLOCK pECB,
PVOID pInfo,
DWORD cbIO,
DWORD dwError)
{
LPSPLASYNC pAsync;
REQINFO ri;
BOOL bRet;
if (pAsync = (LPSPLASYNC)pInfo) {
if ((dwError == 0) && cbIO) {
// Process the return from the IPP-Receive. This will
// process the bytes to the job.
//
bRet = Spl_IppJobData(pECB,
pAsync->wReq,
pAsync->hPrinter,
pAsync->lpszShare,
pAsync->hIpp,
pAsync->lpbBuf,
cbIO,
&pAsync->lpbRet);
// Read another chunk if we haven't read it all yet..
//
pAsync->cbRead += cbIO;
// If an error occured, or we reached the end of our reads,
// then we need to bail out of the asynchronous callback.
//
if ((bRet == FALSE) || (pAsync->cbRead >= pAsync->cbTotal)) {
goto SplCBDone;
}
// Read another chunk.
//
Spl_ReadClient(pECB, pAsync->lpbBuf, pAsync->cbBuf);
} else {
DBGMSG(DBG_WARN, ("Spl_IppJobAsyncCB() : Called with error or zero-bytes\r\n"));
bRet = (pAsync->cbRead >= pAsync->cbTotal);
SplCBDone:
// Send our response-header.
//
WebIppGetReqInfo(pAsync->hIpp, &ri);
Spl_IppJobRsp(pECB, pAsync->wReq, &ri, pAsync->lpbRet);
Spl_FreeAsync(pAsync);
Spl_EndSession(pECB);
}
} else {
DBGMSG(DBG_ERROR, ("Spl_IppJobAsyncCB() : No Context Value\r\n"));
Spl_EndSession(pECB);
}
}
/*****************************************************************************
* Spl_IppJobAsync
*
* This routine processes the job as an asynchronous read.
* It is only called once, the rest of the packets are handled by the async call back.
*
* How IIS's Async reads work:
* 1) ISAPI's HTTPExtensionProc gets called for the first chunk of data as usual.
* 2) In that call:
* - The ISAPI sets up a context, allocs a buffer and registers a call back for
* async reads.
* - Consumes the first chunk of the data.
* - Calls ServerSupportFunction( HSE_REQ_ASYNC_READ_CLIENT...) passing the buffer
* for IIS to write to. This call returns immediately with no data. When
* IIS has got more data from the client, it writes it to the ISAPI's buffer, then
* calls the call back passing the context handle that points to the buffer.
* 3) The call back consumes the data, then calls ServerSupportFunction(
* HSE_REQ_ASYNC_READ_CLIENT) again to repeat the cycle. IIS calls the call back
* once per ISAPI's call to ServerSupportFunction( HSE_REQ_ASYNC_READ_CLIENT ).
*
*****************************************************************************/
DWORD Spl_IppJobAsync(
LPEXTENSION_CONTROL_BLOCK pECB,
WORD wReq,
LPCTSTR lpszShare,
HANDLE hPrinter)
{
LPSPLASYNC pAsync;
REQINFO ri;
BOOL bRet = FALSE;
BOOL bSuccess = FALSE;
// Allocate our structure that contains our state
// info during asynchronous reads.
//
if (pAsync = Spl_AllocAsync(wReq, hPrinter, lpszShare, pECB->cbTotalBytes)) {
// Set our asynchronous callback. Specify our (pAsync) structure
// as the context which is passed to each callback.
//
if (Spl_SetAsyncCB(pECB, (LPVOID)Spl_IppJobAsyncCB, (LPDWORD)pAsync)) {
// Process our first buffer. Our first chunk will utilize
// what's already in the ECB-structure. For other chunks,
// we will specify our own buffer.
//
bSuccess = Spl_IppJobData(pECB,
wReq,
pAsync->hPrinter,
pAsync->lpszShare,
pAsync->hIpp,
pECB->lpbData,
pECB->cbAvailable,
&pAsync->lpbRet);
if (bSuccess) {
// Increment our read-count for the bytes we've
// just processed.
//
pAsync->cbRead += pECB->cbAvailable;
// Do our first asynchronous read. Return if all is
// successful.
//
if (Spl_ReadClient(pECB, pAsync->lpbBuf, pAsync->cbBuf))
return HSE_STATUS_PENDING;
}
WebIppGetReqInfo(pAsync->hIpp, &ri);
Spl_IppJobRsp(pECB, wReq, &ri, pAsync->lpbRet);
Spl_EndSession(pECB);
bRet = TRUE; // We must return HSE_STATUS_PENDING if we call
// HSE_REQ_DONE_WITH_SESSION
}
// Free our async-structure. This indirectly frees the
// return buffer as well.
//
Spl_FreeAsync(pAsync);
} else {
ClosePrinter(hPrinter);
}
return (bRet ? HSE_STATUS_PENDING : HSE_STATUS_ERROR);
}
/*****************************************************************************
* Spl_IppJobSync
*
* This routine processes the job as a synchronous-read. This implies that
* our entire job made it in one-post, and thus doesn't need to perform
* any reads.
*
*****************************************************************************/
DWORD Spl_IppJobSync(
LPEXTENSION_CONTROL_BLOCK pECB,
WORD wReq,
LPCTSTR lpszShare,
HANDLE hPrinter)
{
HANDLE hIpp;
LPIPPRET_JOB pj;
REQINFO ri;
LPBYTE lpbRet = NULL;
BOOL bRet = FALSE;
// Initialize request structure.
//
ZeroMemory(&ri, sizeof(REQINFO));
ri.idReq = 0;
ri.cpReq = CP_UTF8;
ri.pwlUns = NULL;
ri.bFidelity = FALSE;
ri.fReq[0] = IPP_REQALL;
ri.fReq[1] = IPP_REQALL;
// Open an IPP-Receive channel and call the routine to process
// the job.
//
if (hIpp = WebIppRcvOpen(wReq)) {
bRet = Spl_IppJobData(pECB,
wReq,
hPrinter,
lpszShare,
hIpp,
pECB->lpbData,
pECB->cbAvailable,
&lpbRet);
WebIppGetReqInfo(hIpp, &ri);
}
// Send the job-response back to the client. If we weren't able
// to open an IPP-Receive handle, or our job-processing failed,
// then our error is FALSE.
//
bRet = Spl_IppJobRsp(pECB, wReq, &ri, lpbRet);
// Free up the receive-handle only after the response. We need to
// insure the integrity of the unsupported-list-handle.
//
if (hIpp)
WebIppRcvClose(hIpp);
// Close the printer-handle. We do this here as oppose to in
// (msw3prt.cxx), since if we had performed asynchronous reads
// we would need to leave the scope the HttpExtensionProc() call.
//
// NOTE: CloseJob() closes the printer-handle. Only in the case
// where we were not able to open a job should we close it
// here.
//
pj = (LPIPPRET_JOB)lpbRet;
if((wReq == IPP_REQ_PRINTJOB) && pj && pj->bRet) {
CloseJob((DWORD)pj->bRet);
} else {
ClosePrinter(hPrinter);
}
// Free our return-structure.
//
if (lpbRet)
WebIppFreeMem(lpbRet);
return (bRet ? HSE_STATUS_SUCCESS : HSE_STATUS_ERROR);
}
/*****************************************************************************
* SplIppJob
*
* Process the IPP Job Request.
*
* Get the print-job. If we can't handle the entire post within this
* scope, then we setup for asynchronous reads.
*
*****************************************************************************/
DWORD SplIppJob(
WORD wReq,
PALLINFO pAllInfo,
PPRINTERPAGEINFO pPageInfo)
{
DWORD dwRet;
// If our bytes aren't contained in one-chunk, then
// we need to start an asynchronous read.
//
// Otherwise, if our available amounts to the total-bytes
// of the job, then we can process the entire command sychronousely.
//
if (pAllInfo->pECB->cbAvailable < pAllInfo->pECB->cbTotalBytes) {
dwRet = Spl_IppJobAsync(pAllInfo->pECB,
wReq,
pPageInfo->pPrinterInfo->pShareName,
pPageInfo->hPrinter);
} else {
dwRet = Spl_IppJobSync(pAllInfo->pECB,
wReq,
pPageInfo->pPrinterInfo->pShareName,
pPageInfo->hPrinter);
}
return dwRet;
}
/*****************************************************************************
* OpenJob
*
* Starts a job. This creates a new spool-job-entry, the returns a jobid.
*
*
*****************************************************************************/
DWORD OpenJob(
IN LPEXTENSION_CONTROL_BLOCK pECB,
IN HANDLE hPrinter,
IN PIPPREQ_PRTJOB pipr,
IN DWORD dwSize,
OUT PINIJOB *ppCopyIniJob)
{
PINIJOB pIniJob;
DWORD JobId = 0;
LS_HANDLE hLicense;
if ((NULL == hPrinter) || (NULL == pipr) || (dwSize < sizeof(IPPREQ_PRTJOB)))
return 0;
if( RequestLicense( &hLicense, pECB )) { // Enforce the Client Access Licensing
if (pIniJob = (PINIJOB)AllocSplMem(sizeof(INIJOB))) {
DWORD dwNeeded;
DOC_INFO_1 di = {0, 0, 0};
ZeroMemory( pIniJob, sizeof(INIJOB) ); // This ensures that unset fields are NULL
pIniJob->signature = IJ_SIGNATURE;
pIniJob->cb = sizeof(INIJOB);
pIniJob->hLicense = hLicense;
di.pDocName = pipr->pDocument;
if (JobId = StartDocPrinter(hPrinter, 1, (LPBYTE)&di)) {
// we successfully add a job to spooler
//
pIniJob->JobId = JobId;
#if 0
//This is a long, complicated way of doing nothing!
//We do a GetJob with no call to SetJob or any other side effect.
// -- MLAWRENC
// set User name
// =======================================================
// CCTENG 2/5/96
//
// The way we set user name here may not work on NT.
// We do this because we don't have client impersonation.
//
// This also require a UPDATED SPOOLSS.DLL to work,
// it doesn't work on WIN95.
// =======================================================
// MAKE SURE ALL THE LEVEL PARAMETERS ARE THE SAME !!!
//
if (!GetJob(hPrinter, pIniJob->JobId, 1, NULL, 0, &dwNeeded) &&
(GetLastError() == ERROR_INSUFFICIENT_BUFFER))
{
PJOB_INFO_1 pJobInfo;
DWORD cb = dwNeeded;
if(pJobInfo = (PJOB_INFO_1)AllocSplMem(cb)) {
if (GetJob(hPrinter, pIniJob->JobId, 1, (LPBYTE)pJobInfo, cb, &dwNeeded)) {
pJobInfo->pUserName = (pipr->pUserName ? pipr->pUserName : TEXT("Unknown"));
//
// If you do not want to set a printer job's position in that printer queue, you
// should set the Position member to be JOB_POSITION_UNSPECIFIED
//
// weihaic 07/09/98
//pJobInfo->Position = JOB_POSITION_UNSPECIFIED;
//SetJob(hPrinter, pIniJob->JobId, 1, (LPBYTE)pJobInfo, 0);
}
FreeSplMem(pJobInfo, cb);
}
}
#endif
// keep the hPrinter until CloseJob
//
pIniJob->hPrinter = hPrinter;
pIniJob->dwStartTime = GetCurrentMinute();
pIniJob->dwStatus = JOB_READY;
pIniJob->pECB = pECB;
if (ppCopyIniJob)
// Allocate and copy the new ppIniJob structure out, some of the elements
// will be null
if (*ppCopyIniJob = (PINIJOB)AllocSplMem( sizeof(INIJOB) ) )
CopyMemory( *ppCopyIniJob, pIniJob, sizeof(INIJOB) );
AddJobEntry(pIniJob);
} else {
// StartDocPrinter Failed
//
DBGMSG(DBG_WARN, ("StartDocPrinter Failed %d\n", GetLastError()));
FreeSplMem(pIniJob, pIniJob->cb);
FreeLicense( hLicense );
}
}
else // if alloc failed
FreeLicense( hLicense );
} else { // if failed to update a license
// Spl_IppJobRsp() will check for this and send down
// proper error to the client.
//
SetLastError( ERROR_LICENSE_QUOTA_EXCEEDED );
}
#ifdef DEBUG
if (JobId)
DBGMSG(DBG_INFO,("OpenJob : succeed, JobID == %d\r\n", JobId));
else
DBGMSG(DBG_WARN,("OpenJob : failed!\r\n"));
#endif
// what is this for in the failure case ???
//
// AuthenticateUser(pAllInfo);
//
return JobId;
}
/*****************************************************************************
* WriteJob
*
* Write the job.
*
*****************************************************************************/
BOOL WriteJob(
DWORD JobId,
LPBYTE pBuf,
DWORD dwSize,
LPDWORD pWritten)
{
PINIJOB pIniJob;
BOOL bRet = FALSE;
if (pIniJob = FindJob(JobId, JOB_BUSY)) {
// need to add code to check *pWritten == dwSize
// No need to check. The caller will check if all the bytes are written - weihaic
//
bRet = WritePrinter(pIniJob->hPrinter, pBuf, dwSize, pWritten);
pIniJob->dwStatus = JOB_READY;
return bRet;
}
return FALSE;
}
/*****************************************************************************
* CloseJob
*
* Close job and remove from the list.
*
*****************************************************************************/
BOOL CloseJob(
DWORD JobId)
{
PINIJOB pIniJob;
BOOL ret = FALSE;
if (pIniJob = FindJob(JobId, JOB_BUSY)) {
ret = EndDocPrinter(pIniJob->hPrinter);
ClosePrinter(pIniJob->hPrinter);
DeleteJobEntry(pIniJob);
// CleanupOldJob needs to do the same to take care of orphan Async jobs.
FreeLicense( pIniJob->hLicense );
FreeSplMem(pIniJob, pIniJob->cb);
}
return ret;
}
/*****************************************************************************
* DeleteJob
*
* TBD : Unimplemented.
*
*****************************************************************************/
BOOL DeleteJob(
DWORD JobId)
{
return TRUE;
}
/*****************************************************************************
* AddJobEntryToLinkList
*
* Add an entry from a double linked list
*
*****************************************************************************/
VOID AddJobEntryToLinkList(
PINIJOB &pIniFirstJob,
PINIJOB pIniJob)
{
PINIJOB pIniJobTmp;
pIniJob->pNext = NULL;
pIniJob->pPrevious = NULL;
if (!(pIniJobTmp = pIniFirstJob))
{
pIniFirstJob = pIniJob;
}
else
{
// add pIniJob to the end of the list
for (; pIniJobTmp->pNext; pIniJobTmp = pIniJobTmp->pNext)
;
pIniJob->pPrevious = pIniJobTmp;
pIniJobTmp->pNext = pIniJob;
}
}
/*****************************************************************************
* DeleteJobEntryFromLinkList
*
* Delete an entry from a double linked list
*
*****************************************************************************/
VOID DeleteJobEntryFromLinkList(
PINIJOB &pIniFirstJob,
PINIJOB pIniJob)
{
if (pIniJob->pPrevious)
pIniJob->pPrevious->pNext = pIniJob->pNext;
else
// pIniJob must be the first job
pIniFirstJob = pIniJob->pNext;
if (pIniJob->pNext)
pIniJob->pNext->pPrevious = pIniJob->pPrevious;
}
/*****************************************************************************
* AddJobEntry
*
* I just use a simple double linked list for now. Can be changed to
* something else such as a hash table later, if desired.
*
*****************************************************************************/
VOID AddJobEntry(
PINIJOB pIniJob)
{
EnterSplSem();
AddJobEntryToLinkList(pIniFirstJob, pIniJob);
LeaveSplSem();
}
/*****************************************************************************
* DeleteJobEntry
*
* Delete job from the job-list.
*
*****************************************************************************/
VOID DeleteJobEntry(
PINIJOB pIniJob)
{
EnterSplSem();
DeleteJobEntryFromLinkList (pIniFirstJob, pIniJob);
LeaveSplSem();
}
/*****************************************************************************
*
* FindJob
*
* Looks for job in the job-list and dwStatus
*
*****************************************************************************/
PINIJOB FindJob(
DWORD JobId,
DWORD dwStatus)
{
PINIJOB pIniJob;
EnterSplSem();
// pIniJob will end up being NULL if a match is not found
for (pIniJob = pIniFirstJob; pIniJob; pIniJob = pIniJob->pNext)
{
if (pIniJob->dwStatus == JOB_READY && pIniJob->JobId == JobId) {
// found the match, break and return pIniJob
// Set the status
pIniJob->dwStatus = dwStatus;
break;
}
}
LeaveSplSem();
return pIniJob;
}
/*****************************************************************************
* CleanupOldJob
*
* This function is called by Sleeper->Work() about every 15 minutes to cleanup
* the pending unclosed jobs due to the failure of the network or any other
* possible reasons.
*
*****************************************************************************/
BOOL CleanupOldJob()
{
DWORD dwCurrentTime = GetCurrentMinute();
PINIJOB pIniJob;
PINIJOB pIniTmpJob;
PINIJOB pIniFirstOldJob = NULL;
if (!pIniFirstJob) return TRUE;
DBGMSG (DBG_WARN, ("Enter Cleanup...\r\n"));
EnterSplSem();
for (pIniJob = pIniFirstJob; pIniJob; pIniJob = pIniTmpJob) {
pIniTmpJob = pIniJob->pNext;
if (pIniJob->dwStatus == JOB_READY) {
DWORD dwDiff = (1440 + dwCurrentTime - pIniJob->dwStartTime) % 1440;
if (dwDiff > MAX_JOB_MINUTE) {
DBGMSG (DBG_WARN, ("OldJob found %x\r\n", pIniJob->hPrinter));
DeleteJobEntry (pIniJob);
AddJobEntryToLinkList (pIniFirstOldJob, pIniJob);
}
}
}
LeaveSplSem();
DWORD dwStatus = HTTP_STATUS_REQUEST_TIMEOUT;
// Delete the job outside the critical section
for (pIniJob = pIniFirstOldJob; pIniJob; pIniJob = pIniTmpJob) {
pIniTmpJob = pIniJob->pNext;
EndDocPrinter(pIniJob->hPrinter);
ClosePrinter(pIniJob->hPrinter);
FreeLicense( pIniJob->hLicense ); // CleanupOldJob needs to do the same to take care of orphan Async jobs.
#ifdef ASYNC_READ_ENABLED
// Disable it because we're not trying to manage the cleanup for
// http sessions. If there is a session pending because we close
// the job, the callback function (Spl_JobPrintCB)
// will close the session itself.
//
pIniJob->pECB->ServerSupportFunction(pIniJob->pECB->ConnID,
HSE_REQ_DONE_WITH_SESSION,
&dwStatus,
NULL,
NULL);
#endif
FreeSplMem(pIniJob, pIniJob->cb);
}
return TRUE;
}
/*****************************************************************************
* GetCurrentMinute
*
* Get the current minute since midnight
*
*****************************************************************************/
DWORD GetCurrentMinute ()
{
SYSTEMTIME CurTime;
GetSystemTime (&CurTime);
return CurTime.wHour * 60 + CurTime.wMinute;
}
#ifdef DEBUG
DWORD dwSplHeapSize = 0;
/*****************************************************************************
* AllocSplMem (Helper)
*
* Routine Description:
*
* This function will allocate local memory. It will possibly allocate extra
* memory and fill this with debugging information for the debugging version.
*
* Arguments:
*
* cb - The amount of memory to allocate
*
* Return Value:
*
* NON-NULL - A pointer to the allocated memory
*
* FALSE/NULL - The operation failed. Extended error status is available
* using GetLastError.
*
*
*****************************************************************************/
LPVOID AllocSplMem(
DWORD cb)
{
PDWORD pMem;
DWORD cbNew;
cbNew = cb+2*sizeof(DWORD);
if (cbNew & 3)
cbNew += sizeof(DWORD) - (cbNew & 3);
pMem=(PDWORD)LocalAlloc(LPTR, cbNew);
if (!pMem)
{
DBGMSG(DBG_ERROR, ("Memory Allocation failed for %d bytes\n", cbNew));
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return 0;
}
*pMem=cb;
*(PDWORD)((PBYTE)pMem+cbNew-sizeof(DWORD))=0xdeadbeef;
dwSplHeapSize += cbNew;
return (LPVOID)(pMem+1);
}
/*****************************************************************************
* FreeSplMem (Helper)
*
*
*****************************************************************************/
BOOL FreeSplMem(
LPVOID pMem,
DWORD cb)
{
DWORD cbNew;
LPDWORD pNewMem;
if (!pMem)
return FALSE;
pNewMem = (LPDWORD)pMem;
pNewMem--;
cbNew = cb+2*sizeof(DWORD);
if (cbNew & 3)
cbNew += sizeof(DWORD) - (cbNew & 3);
if (*pNewMem != cb) {
DBGMSG(DBG_ERROR, ("Corrupt Memory Size in inetsrv-spool : %0lx %0lx != %0lx\n",
pNewMem, *pNewMem, cb));
return FALSE;
}
if (*(LPDWORD)((LPBYTE)pNewMem + cbNew - sizeof(DWORD)) != 0xdeadbeef) {
DBGMSG(DBG_ERROR, ("Memory Overrun in inetsrv-spool : %0lx\n", pNewMem));
return FALSE;
}
LocalFree((LPVOID)pNewMem);
dwSplHeapSize -= cbNew;
return TRUE;
}
#endif // DEBUG
/*****************************************************************************
* AllocSplStr (Helper)
*
* Routine Description:
*
* This function will allocate enough local memory to store the specified
* string, and copy that string to the allocated memory
*
* Arguments:
*
* pStr - Pointer to the string that needs to be allocated and stored
*
* Return Value:
*
* NON-NULL - A pointer to the allocated memory containing the string
*
* FALSE/NULL - The operation failed. Extended error status is available
* using GetLastError.
*****************************************************************************/
LPTSTR AllocSplStr(
LPCTSTR lpszStr)
{
DWORD cbSize;
LPTSTR lpszCpy = NULL;
if (cbSize = Spl_StrSize(lpszStr)) {
if (lpszCpy = (LPTSTR)AllocSplMem(cbSize))
CopyMemory((PVOID)lpszCpy, (PVOID)lpszStr, cbSize);
}
return lpszCpy;
}
/*****************************************************************************
* FreeSplStr (Helper)
*
*
*****************************************************************************/
#ifdef DEBUG
#define FREE_PTR_TO_LONG(X) (X)
#else
#define FREE_PTR_TO_LONG(X) (PtrToLong(X))
#endif
BOOL FreeSplStr(
LPTSTR lpszStr)
{
DWORD cbSize;
cbSize = Spl_StrSize(lpszStr);
return (BOOL)(lpszStr ? FREE_PTR_TO_LONG(FreeSplMem(lpszStr, cbSize)) : FALSE);
}
/*****************************************************************************
* AuthenticateUser (Helper)
*
*
*****************************************************************************/
BOOL AuthenticateUser(
PALLINFO pAllInfo)
{
// Wade says if we don't specify a header (szAuthHdr), and just submit a 401 error, IIS
// would include (in the automatically generated header) what authenticaitons it is setup
// to use (NTLM and/or Basic). So the client can pick the first one on the list and use it
// (this is what IE does).
//
// Note: for NTLM to work, you need adirect socket connection. So it won't work across
// firewalls (IIS admins are supposed to know this). Secure socket seems to do it though,
// so for the new MS Proxy, it might be doable.
//
return Spl_CallSSF(pAllInfo->pECB,
HSE_REQ_SEND_RESPONSE_HEADER,
(LPVOID)"401 Authentication Required",
(LPDWORD)NULL,
(LPDWORD)NULL);
}