Beispiel #1
0
void
FreeJob(PLOCAL_JOB pJob)
{
    PWSTR pwszSHDFile;
    
    // Remove the Job from both Job Lists.
    DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
    DeleteElementSkiplist(&GlobalJobList, pJob);

    // Try to delete the corresponding .SHD file.
    pwszSHDFile = DllAllocSplMem(GetJobFilePath(L"SHD", 0, NULL));
    if (pwszSHDFile && GetJobFilePath(L"SHD", pJob->dwJobID, pwszSHDFile))
        DeleteFileW(pwszSHDFile);

    // Free memory for the mandatory fields.
    DllFreeSplMem(pJob->pDevMode);
    DllFreeSplStr(pJob->pwszDatatype);
    DllFreeSplStr(pJob->pwszDocumentName);
    DllFreeSplStr(pJob->pwszMachineName);
    DllFreeSplStr(pJob->pwszNotifyName);
    DllFreeSplStr(pJob->pwszUserName);

    // Free memory for the optional fields if they are present.
    if (pJob->pwszOutputFile)
        DllFreeSplStr(pJob->pwszOutputFile);
    
    if (pJob->pwszPrintProcessorParameters)
        DllFreeSplStr(pJob->pwszPrintProcessorParameters);

    if (pJob->pwszStatus)
        DllFreeSplStr(pJob->pwszStatus);

    // Finally free the job structure itself.
    DllFreeSplMem(pJob);
}
Beispiel #2
0
/**
 * @name _HandleSetDefaultCommConfig
 *
 * Sets the default configuration of a legacy port. Checks for granted SERVER_ACCESS_ADMINISTER access.
 * You have to supply the port name (with colon!) in XcvOpenPort.
 * The opposite function is _HandleGetDefaultCommConfig.
 *
 * @param pXcv
 * Pointer to the LOCALMON_XCV structure of the currently opened Xcv port.
 *
 * @param pInputData
 * Pointer to the COMMCONFIG structure that shall be passed to SetDefaultCommConfigW.
 *
 * @param pcbOutputNeeded
 * Pointer to a DWORD that will be zeroed on return.
 *
 * @return
 * An error code indicating success or failure.
 */
static DWORD
_HandleSetDefaultCommConfig(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
{
    DWORD dwErrorCode;
    HANDLE hToken = NULL;
    LPCOMMCONFIG pCommConfig;
    PWSTR pwszPortNameWithoutColon = NULL;

    // Sanity checks
    // pwszObject needs to be at least 2 characters long to be a port name with a trailing colon.
    if (!pXcv || !pXcv->pwszObject || !pXcv->pwszObject[0] || !pXcv->pwszObject[1] || !pInputData || !pcbOutputNeeded)
    {
        dwErrorCode = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    *pcbOutputNeeded = 0;

    // This action can only happen at SERVER_ACCESS_ADMINISTER access level.
    if (!(pXcv->GrantedAccess & SERVER_ACCESS_ADMINISTER))
    {
        dwErrorCode = ERROR_ACCESS_DENIED;
        goto Cleanup;
    }

    // SetDefaultCommConfigW needs the port name without colon.
    dwErrorCode = GetPortNameWithoutColon(pXcv->pwszObject, &pwszPortNameWithoutColon);
    if (dwErrorCode != ERROR_SUCCESS)
        goto Cleanup;

    // Switch to the SYSTEM context for setting the port configuration.
    hToken = RevertToPrinterSelf();
    if (!hToken)
    {
        dwErrorCode = GetLastError();
        ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode);
        goto Cleanup;
    }

    // Finally pass the parameters to SetDefaultCommConfigW.
    pCommConfig = (LPCOMMCONFIG)pInputData;
    if (!SetDefaultCommConfigW(pwszPortNameWithoutColon, pCommConfig, pCommConfig->dwSize))
    {
        dwErrorCode = GetLastError();
        ERR("SetDefaultCommConfigW failed with error %lu!\n", dwErrorCode);
        goto Cleanup;
    }

    dwErrorCode = ERROR_SUCCESS;

Cleanup:
    if (hToken)
        ImpersonatePrinterClient(hToken);

    if (pwszPortNameWithoutColon)
        DllFreeSplMem(pwszPortNameWithoutColon);

    return dwErrorCode;
}
Beispiel #3
0
BOOL WINAPI
LocalmonXcvClosePort(HANDLE hXcv)
{
    PLOCALMON_XCV pXcv = (PLOCALMON_XCV)hXcv;

    TRACE("LocalmonXcvClosePort(%p)\n", hXcv);

    // Sanity checks
    if (!pXcv)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    // Remove it from the list and free the memory.
    RemoveEntryList(&pXcv->Entry);
    DllFreeSplMem(pXcv);

    SetLastError(ERROR_SUCCESS);
    return TRUE;
}
Beispiel #4
0
BOOL WINAPI
ClosePrinter(HANDLE hPrinter)
{
    BOOL bReturnValue;
    PSPOOLSS_PRINTER_HANDLE pHandle = (PSPOOLSS_PRINTER_HANDLE)hPrinter;

    // Sanity checks.
    if (!pHandle)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    // FIXME: Call FindClosePrinterChangeNotification for all created change notifications (according to MSDN).

    // Call CloseHandle of the Print Provider.
    bReturnValue = pHandle->pPrintProvider->PrintProvider.fpClosePrinter(pHandle->hPrinter);

    // Free our handle information.
    DllFreeSplMem(pHandle);

    return bReturnValue;
}
Beispiel #5
0
BOOL
InitializePortList()
{
    BOOL bReturnValue;
    DWORD cbNeeded;
    DWORD cbPortName;
    DWORD dwErrorCode;
    DWORD dwReturned;
    DWORD i;
    PLOCAL_PORT pPort;
    PLOCAL_PRINT_MONITOR pPrintMonitor;
    PLIST_ENTRY pEntry;
    PPORT_INFO_1W p;
    PPORT_INFO_1W pPortInfo1 = NULL;

    // Initialize an empty list for our Ports.
    InitializeListHead(&_PortList);

    // Loop through all Print Monitors.
    for (pEntry = PrintMonitorList.Flink; pEntry != &PrintMonitorList; pEntry = pEntry->Flink)
    {
        // Cleanup from the previous run.
        if (pPortInfo1)
        {
            DllFreeSplMem(pPortInfo1);
            pPortInfo1 = NULL;
        }

        pPrintMonitor = CONTAINING_RECORD(pEntry, LOCAL_PRINT_MONITOR, Entry);

        // Determine the required buffer size for EnumPorts.
        if (pPrintMonitor->bIsLevel2)
            bReturnValue = ((PMONITOR2)pPrintMonitor->pMonitor)->pfnEnumPorts(pPrintMonitor->hMonitor, NULL, 1, NULL, 0, &cbNeeded, &dwReturned);
        else
            bReturnValue = ((LPMONITOREX)pPrintMonitor->pMonitor)->Monitor.pfnEnumPorts(NULL, 1, NULL, 0, &cbNeeded, &dwReturned);

        // Check the returned error code.
        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
            ERR("Print Monitor \"%S\" failed with error %lu on EnumPorts!\n", pPrintMonitor->pwszName, GetLastError());
            continue;
        }

        // Allocate a buffer large enough.
        pPortInfo1 = DllAllocSplMem(cbNeeded);
        if (!pPortInfo1)
        {
            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
            ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
            goto Cleanup;
        }

        // Get the ports handled by this monitor.
        if (pPrintMonitor->bIsLevel2)
            bReturnValue = ((PMONITOR2)pPrintMonitor->pMonitor)->pfnEnumPorts(pPrintMonitor->hMonitor, NULL, 1, (PBYTE)pPortInfo1, cbNeeded, &cbNeeded, &dwReturned);
        else
            bReturnValue = ((LPMONITOREX)pPrintMonitor->pMonitor)->Monitor.pfnEnumPorts(NULL, 1, (PBYTE)pPortInfo1, cbNeeded, &cbNeeded, &dwReturned);

        // Check the return value.
        if (!bReturnValue)
        {
            ERR("Print Monitor \"%S\" failed with error %lu on EnumPorts!\n", pPrintMonitor->pwszName, GetLastError());
            continue;
        }

        // Loop through all returned ports.
        p = pPortInfo1;

        for (i = 0; i < dwReturned; i++)
        {
            cbPortName = (wcslen(p->pName) + 1) * sizeof(WCHAR);

            // Create a new LOCAL_PORT structure for it.
            pPort = DllAllocSplMem(sizeof(LOCAL_PORT) + cbPortName);
            if (!pPort)
            {
                dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
                ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
                goto Cleanup;
            }

            pPort->pPrintMonitor = pPrintMonitor;
            pPort->pwszName = (PWSTR)((PBYTE)pPort + sizeof(LOCAL_PORT));
            CopyMemory(pPort->pwszName, p->pName, cbPortName);

            // Insert it into the list and advance to the next port.
            InsertTailList(&_PortList, &pPort->Entry);
            p++;
        }
    }

    dwErrorCode = ERROR_SUCCESS;

Cleanup:
    // Inside the loop
    if (pPortInfo1)
        DllFreeSplMem(pPortInfo1);

    SetLastError(dwErrorCode);
    return (dwErrorCode == ERROR_SUCCESS);
}
Beispiel #6
0
DWORD WINAPI
CreateJob(PLOCAL_PRINTER_HANDLE pPrinterHandle)
{
    const WCHAR wszDoubleBackslash[] = L"\\";
    const DWORD cchDoubleBackslash = _countof(wszDoubleBackslash) - 1;

    DWORD cchMachineName;
    DWORD cchUserName;
    DWORD dwErrorCode;
    PLOCAL_JOB pJob;
    RPC_BINDING_HANDLE hServerBinding = NULL;
    RPC_WSTR pwszBinding = NULL;
    RPC_WSTR pwszMachineName = NULL;

    // Create a new job.
    pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
    if (!pJob)
    {
        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
        goto Cleanup;
    }

    // Reserve an ID for this job.
    if (!_GetNextJobID(&pJob->dwJobID))
    {
        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }

    // Copy over defaults to the LOCAL_JOB structure.
    pJob->pPrinter = pPrinterHandle->pPrinter;
    pJob->pPrintProcessor = pPrinterHandle->pPrinter->pPrintProcessor;
    pJob->dwPriority = DEF_PRIORITY;
    pJob->dwStatus = JOB_STATUS_SPOOLING;
    pJob->pwszDatatype = AllocSplStr(pPrinterHandle->pwszDatatype);
    pJob->pwszDocumentName = AllocSplStr(wszDefaultDocumentName);
    pJob->pDevMode = DuplicateDevMode(pPrinterHandle->pDevMode);
    GetSystemTime(&pJob->stSubmitted);

    // Get the user name for the Job.
    cchUserName = UNLEN + 1;
    pJob->pwszUserName = DllAllocSplMem(cchUserName * sizeof(WCHAR));
    if (!GetUserNameW(pJob->pwszUserName, &cchUserName))
    {
        dwErrorCode = GetLastError();
        ERR("GetUserNameW failed with error %lu!\n", dwErrorCode);
        goto Cleanup;
    }

    // FIXME: For now, pwszNotifyName equals pwszUserName.
    pJob->pwszNotifyName = AllocSplStr(pJob->pwszUserName);

    // Get the name of the machine that submitted the Job over RPC.
    dwErrorCode = RpcBindingServerFromClient(NULL, &hServerBinding);
    if (dwErrorCode != RPC_S_OK)
    {
        ERR("RpcBindingServerFromClient failed with status %lu!\n", dwErrorCode);
        goto Cleanup;
    }

    dwErrorCode = RpcBindingToStringBindingW(hServerBinding, &pwszBinding);
    if (dwErrorCode != RPC_S_OK)
    {
        ERR("RpcBindingToStringBindingW failed with status %lu!\n", dwErrorCode);
        goto Cleanup;
    }

    dwErrorCode = RpcStringBindingParseW(pwszBinding, NULL, NULL, &pwszMachineName, NULL, NULL);
    if (dwErrorCode != RPC_S_OK)
    {
        ERR("RpcStringBindingParseW failed with status %lu!\n", dwErrorCode);
        goto Cleanup;
    }

    cchMachineName = wcslen(pwszMachineName);
    pJob->pwszMachineName = DllAllocSplMem((cchMachineName + cchDoubleBackslash + 1) * sizeof(WCHAR));
    CopyMemory(pJob->pwszMachineName, wszDoubleBackslash, cchDoubleBackslash * sizeof(WCHAR));
    CopyMemory(&pJob->pwszMachineName[cchDoubleBackslash], pwszMachineName, (cchMachineName + 1) * sizeof(WCHAR));

    // Add the job to the Global Job List.
    if (!InsertElementSkiplist(&GlobalJobList, pJob))
    {
        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
        ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
        goto Cleanup;
    }

    // Add the job at the end of the Printer's Job List.
    // As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
    if (!InsertTailElementSkiplist(&pJob->pPrinter->JobList, pJob))
    {
        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
        ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
        goto Cleanup;
    }

    // We were successful!
    pPrinterHandle->bStartedDoc = TRUE;
    pPrinterHandle->pJob = pJob;
    dwErrorCode = ERROR_SUCCESS;

    // Don't let the cleanup routine free this.
    pJob = NULL;

Cleanup:
    if (pJob)
        DllFreeSplMem(pJob);

    if (pwszMachineName)
        RpcStringFreeW(&pwszMachineName);

    if (pwszBinding)
        RpcStringFreeW(&pwszBinding);

    if (hServerBinding)
        RpcBindingFree(&hServerBinding);

    return dwErrorCode;
}
Beispiel #7
0
BOOL
WriteJobShadowFile(PWSTR pwszFilePath, const PLOCAL_JOB pJob)
{
    BOOL bReturnValue = FALSE;
    DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
    DWORD cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
    DWORD cbDocumentName = 0;
    DWORD cbFileSize;
    DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
    DWORD cbNotifyName = 0;
    DWORD cbPrinterDriver = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
    DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
    DWORD cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
    DWORD cbPrintProcessorParameters = 0;
    DWORD cbUserName = 0;
    DWORD cbWritten;
    DWORD dwCurrentOffset;
    HANDLE hSHDFile = INVALID_HANDLE_VALUE;
    HANDLE hSPLFile = INVALID_HANDLE_VALUE;
    PSHD_HEADER pShadowFile = NULL;

    // Try to open the SHD file.
    hSHDFile = CreateFileW(pwszFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
    if (hSHDFile == INVALID_HANDLE_VALUE)
    {
        ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
        goto Cleanup;
    }

    // Calculate the lengths of the optional values and the total size of the shadow file.
    if (pJob->pwszDocumentName)
        cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);

    if (pJob->pwszNotifyName)
        cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);

    if (pJob->pwszPrintProcessorParameters)
        cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);

    if (pJob->pwszUserName)
        cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);

    cbFileSize = sizeof(SHD_HEADER) + cbDatatype + cbDocumentName + cbDevMode + cbMachineName + cbNotifyName + cbPrinterDriver + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbUserName;

    // Allocate memory for it.
    pShadowFile = DllAllocSplMem(cbFileSize);
    if (!pShadowFile)
    {
        ERR("DllAllocSplMem failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
        goto Cleanup;
    }

    // Fill out the shadow file header information.
    pShadowFile->dwSignature = SHD_WIN2003_SIGNATURE;
    pShadowFile->cbHeader = sizeof(SHD_HEADER);

    // Copy the values.
    pShadowFile->dwJobID = pJob->dwJobID;
    pShadowFile->dwPriority = pJob->dwPriority;
    pShadowFile->dwStartTime = pJob->dwStartTime;
    pShadowFile->dwTotalPages = pJob->dwTotalPages;
    pShadowFile->dwUntilTime = pJob->dwUntilTime;
    CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));

    // Determine the file size of the .SPL file
    wcscpy(wcsrchr(pwszFilePath, L'.'), L".SPL");
    hSPLFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (hSPLFile != INVALID_HANDLE_VALUE)
        pShadowFile->dwSPLSize = GetFileSize(hSPLFile, NULL);

    // Add the extra values that are stored as offsets in the shadow file.
    // The first value begins right after the shadow file header.
    dwCurrentOffset = sizeof(SHD_HEADER);

    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
    pShadowFile->offDatatype = dwCurrentOffset;
    dwCurrentOffset += cbDatatype;

    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pDevMode, cbDevMode);
    pShadowFile->offDevMode = dwCurrentOffset;
    dwCurrentOffset += cbDevMode;

    // offDriverName is only written, but automatically determined through offPrinterName when reading.
    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterDriver, cbPrinterDriver);
    pShadowFile->offDriverName = dwCurrentOffset;
    dwCurrentOffset += cbPrinterDriver;

    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszMachineName, cbMachineName);
    pShadowFile->offMachineName = dwCurrentOffset;
    dwCurrentOffset += cbMachineName;

    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterName, cbPrinterName);
    pShadowFile->offPrinterName = dwCurrentOffset;
    dwCurrentOffset += cbPrinterName;

    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
    pShadowFile->offPrintProcessor = dwCurrentOffset;
    dwCurrentOffset += cbPrintProcessor;

    // Copy the optional values.
    if (cbDocumentName)
    {
        CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDocumentName, cbDocumentName);
        pShadowFile->offDocumentName = dwCurrentOffset;
        dwCurrentOffset += cbDocumentName;
    }

    if (cbNotifyName)
    {
        CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszNotifyName, cbNotifyName);
        pShadowFile->offNotifyName = dwCurrentOffset;
        dwCurrentOffset += cbNotifyName;
    }

    if (cbPrintProcessorParameters)
    {
        CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
        pShadowFile->offPrintProcessorParameters = dwCurrentOffset;
        dwCurrentOffset += cbPrintProcessorParameters;
    }

    if (cbUserName)
    {
        CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszUserName, cbUserName);
        pShadowFile->offUserName = dwCurrentOffset;
        dwCurrentOffset += cbUserName;
    }

    // Write the file.
    if (!WriteFile(hSHDFile, pShadowFile, cbFileSize, &cbWritten, NULL))
    {
        ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
        goto Cleanup;
    }

    bReturnValue = TRUE;

Cleanup:
    if (pShadowFile)
        DllFreeSplMem(pShadowFile);

    if (hSHDFile != INVALID_HANDLE_VALUE)
        CloseHandle(hSHDFile);

    if (hSPLFile != INVALID_HANDLE_VALUE)
        CloseHandle(hSPLFile);

    return bReturnValue;
}
Beispiel #8
0
PLOCAL_JOB
ReadJobShadowFile(PCWSTR pwszFilePath)
{
    DWORD cbFileSize;
    DWORD cbRead;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    PLOCAL_JOB pJob;
    PLOCAL_JOB pReturnValue = NULL;
    PLOCAL_PRINTER pPrinter;
    PLOCAL_PRINT_PROCESSOR pPrintProcessor;
    PSHD_HEADER pShadowFile = NULL;
    PWSTR pwszPrinterName;
    PWSTR pwszPrintProcessor;

    // Try to open the file.
    hFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
        goto Cleanup;
    }

    // Get its file size (small enough for a single DWORD) and allocate memory for all of it.
    cbFileSize = GetFileSize(hFile, NULL);
    pShadowFile = DllAllocSplMem(cbFileSize);
    if (!pShadowFile)
    {
        ERR("DllAllocSplMem failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
        goto Cleanup;
    }

    // Read the entire file.
    if (!ReadFile(hFile, pShadowFile, cbFileSize, &cbRead, NULL))
    {
        ERR("ReadFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
        goto Cleanup;
    }

    // Check signature and header size.
    if (pShadowFile->dwSignature != SHD_WIN2003_SIGNATURE || pShadowFile->cbHeader != sizeof(SHD_HEADER))
    {
        ERR("Signature or Header Size mismatch for file \"%S\"!\n", pwszFilePath);
        goto Cleanup;
    }

    // Retrieve the associated printer from the list.
    pwszPrinterName = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrinterName);
    pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
    if (!pPrinter)
    {
        ERR("Shadow file \"%S\" references a non-existing printer \"%S\"!\n", pwszFilePath, pwszPrinterName);
        goto Cleanup;
    }

    // Retrieve the associated Print Processor from the list.
    pwszPrintProcessor = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessor);
    pPrintProcessor = FindPrintProcessor(pwszPrintProcessor);
    if (!pPrintProcessor)
    {
        ERR("Shadow file \"%S\" references a non-existing Print Processor \"%S\"!\n", pwszFilePath, pwszPrintProcessor);
        goto Cleanup;
    }

    // Create a new job structure and copy over the relevant fields.
    pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
    if (!pJob)
    {
        ERR("DllAllocSplMem failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
        goto Cleanup;
    }

    pJob->dwJobID = pShadowFile->dwJobID;
    pJob->dwPriority = pShadowFile->dwPriority;
    pJob->dwStartTime = pShadowFile->dwStartTime;
    pJob->dwTotalPages = pShadowFile->dwTotalPages;
    pJob->dwUntilTime = pShadowFile->dwUntilTime;
    pJob->pPrinter = pPrinter;
    pJob->pPrintProcessor = pPrintProcessor;
    pJob->pDevMode = DuplicateDevMode((PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode));
    pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
    pJob->pwszMachineName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offMachineName));
    CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));

    // Copy the optional values.
    if (pShadowFile->offDocumentName)
        pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));

    if (pShadowFile->offNotifyName)
        pJob->pwszNotifyName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offNotifyName));

    if (pShadowFile->offPrintProcessorParameters)
        pJob->pwszPrintProcessorParameters = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessorParameters));

    if (pShadowFile->offUserName)
        pJob->pwszUserName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offUserName));

    // Jobs read from shadow files were always added using AddJob.
    pJob->bAddedJob = TRUE;

    pReturnValue = pJob;

Cleanup:
    if (pShadowFile)
        DllFreeSplMem(pShadowFile);

    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    return pReturnValue;
}