/*****************************************************************************\ * MODULE: gencab.c * * The module contains routines for generating a self-extracting CAB file * for the IExpress utility. This relies on the cdfCreate and infCreate * to create the objects for the IExpress process. * * * Copyright (C) 1996-1997 Microsoft Corporation * Copyright (C) 1996-1997 Hewlett Packard * * History: * 22-Nov-1996 Created. * \*****************************************************************************/ #include "pch.h" #include "spool.h" #include // To define the IIS Metabase GUIDs used in this file. Every other file defines GUIDs as extern, except here where we actually define them in initguid.h. #include // Interface header #include // MD_ & IIS_MD_ defines #define CAB_TIMEOUT 1000 /*****************************************************************************\ * Critical Section Handling * \*****************************************************************************/ DWORD gdwCritOwner = 0; VOID cab_enter_crit(VOID) { EnterCriticalSection(&g_csGenCab); gdwCritOwner = GetCurrentThreadId(); } VOID cab_leave_crit(VOID) { gdwCritOwner = 0; LeaveCriticalSection(&g_csGenCab); } VOID cab_check_crit(VOID) { DWORD dwCurrent = GetCurrentThreadId(); SPLASSERT((dwCurrent == gdwCritOwner)); } #define EnterCABCrit() cab_enter_crit() #define LeaveCABCrit() cab_leave_crit() #define CheckCABCrit() cab_check_crit() /*****************************************************************************\ * cab_SetClientSecurity (Local Routine) * * This routine sets the thread security so that it has max-privileges. * \*****************************************************************************/ HANDLE cab_SetClientSecurity(VOID) { HANDLE hToken; HANDLE hThread = GetCurrentThread(); if (OpenThreadToken(hThread, TOKEN_IMPERSONATE, TRUE, &hToken)) { if (SetThreadToken(&hThread, NULL)) { return hToken; } CloseHandle(hToken); } return NULL; } /*****************************************************************************\ * cab_ResetClientSecurity (Local Routine) * * This routine resets the client-security to the token passed in. This * is called in order to set the thread to the original security attributes. * \*****************************************************************************/ BOOL cab_ResetClientSecurity( HANDLE hToken) { BOOL bRet; bRet = SetThreadToken(NULL, hToken); CloseHandle(hToken); return bRet; } /*****************************************************************************\ * cab_iexpress_sync (Local Routine) * * This routine sychronizes the process and won't return until the process * is done generating the CAB executable. * \*****************************************************************************/ BOOL cab_iexpress_sync( HANDLE hProcess) { DWORD dwObj; DWORD dwExitCode; while (TRUE) { dwObj = WaitForSingleObject(hProcess, INFINITE); // Look for the exit type. // switch (dwObj) { // The process handle triggered the wait. Let's get the // exit-code and return whether the success. Otherwise, // drop through and return the failure. // case WAIT_OBJECT_0: GetExitCodeProcess(hProcess, &dwExitCode); if (dwExitCode == 0) return TRUE; // Something failed in the call. We failed. // case WAIT_FAILED: return FALSE; } } } /*****************************************************************************\ * cab_force_delete_file * * Reset a files attributes before deleting it. We copied them to the temp dir * so we can delete them no matter what. * \*****************************************************************************/ VOID cab_force_delete_file( LPCTSTR lpszFileName) { SetFileAttributes( lpszFileName, FILE_ATTRIBUTE_NORMAL ); DeleteFile( lpszFileName ); } /*****************************************************************************\ * cab_cleanup_files * * Cleanup any files left-over in our generation process. Particularly, * this will remove cab-files. * \*****************************************************************************/ VOID cab_cleanup_files( LPCTSTR lpszDstPath) { TCHAR szAllExt[MIN_CAB_BUFFER]; HANDLE hFind; DWORD idx; DWORD cItems; LPTSTR lpszCurDir; WIN32_FIND_DATA wfd; static LPCTSTR s_szFiles[] = { g_szDotInf, g_szDotBin, g_szDotCat }; // Put us into the destination directory where we // want to cleanup the files. // if (lpszCurDir = genGetCurDir()) { SetCurrentDirectory(lpszDstPath); // Delete the temporary dat-file used in building // the cdf-file // cab_force_delete_file(g_szDatFile); // Delete certain extension files. // cItems = sizeof(s_szFiles) / sizeof(s_szFiles[0]); for (idx = 0; idx < cItems; idx++) { wsprintf(szAllExt, TEXT("*%s"), s_szFiles[idx]); hFind = FindFirstFile(szAllExt, &wfd); if (hFind && (hFind != INVALID_HANDLE_VALUE)) { do { cab_force_delete_file(wfd.cFileName); } while (FindNextFile(hFind, &wfd)); FindClose(hFind); } } SetCurrentDirectory(lpszCurDir); genGFree(lpszCurDir, genGSize(lpszCurDir)); } return; } /*****************************************************************************\ * cab_rename_cab * * Renames the .CAB to the dstname. * \*****************************************************************************/ BOOL cab_rename_cab( LPCTSTR lpszDstName) { LPTSTR lpszSrc; LPTSTR lpszDst; BOOL bRet = FALSE; if (lpszSrc = genBuildFileName(NULL, lpszDstName, g_szDotCab)) { if (lpszDst = genBuildFileName(NULL, lpszDstName, g_szDotIpp)) { bRet = MoveFileEx(lpszSrc, lpszDst, MOVEFILE_REPLACE_EXISTING); genGFree(lpszDst, genGSize(lpszDst)); } genGFree(lpszSrc, genGSize(lpszSrc)); } return bRet; } /*****************************************************************************\ * cab_iexpress_exec (Local Routine) * * This exec's the IExpress utility to generate the self-extracting CAB * file. * \*****************************************************************************/ BOOL cab_iexpress_exec( LPCTSTR lpszCabDir, LPCTSTR lpszDstName, LPCTSTR lpszSedFile) { PROCESS_INFORMATION pi; STARTUPINFO sti; LPTSTR lpszCmd; DWORD cbSize; LPTSTR lpszOldDir; BOOL bSuccess = FALSE; TCHAR szWindowDir[MAX_PATH]; DWORD dwWinDirLen = 0; if (dwWinDirLen = GetSystemWindowsDirectory (szWindowDir, MAX_PATH)) { if (szWindowDir[dwWinDirLen - 1] == TEXT ('\\')) { szWindowDir[dwWinDirLen - 1] = 0; } // Calculate enough space to hold the command-line arguments. // cbSize = (dwWinDirLen + lstrlen(lpszSedFile) + lstrlen(g_szCabCmd) + 1) * sizeof(TCHAR); // Allocate the command-line for the create-process call. // if (lpszCmd = (LPTSTR)genGAlloc(cbSize)) { // Initialize startup-info fields. // memset(&sti, 0, sizeof(STARTUPINFO)); sti.cb = sizeof(STARTUPINFO); // Build the command-line string that exec's IExpress. // wsprintf(lpszCmd, g_szCabCmd, szWindowDir, lpszSedFile); // Change the directory to the cab/sed directory. It // appears that IExpress requires this to generate. // if (lpszOldDir = genGetCurDir()) { SetCurrentDirectory(lpszCabDir); // Exec the process. // if (EXEC_PROCESS(lpszCmd, &sti, &pi)) { CloseHandle(pi.hThread); // This will wait until the process if finished generating // the file. The return from this indicates whether the // generation succeeded or not. // if (cab_iexpress_sync(pi.hProcess)) bSuccess = cab_rename_cab(lpszDstName); CloseHandle(pi.hProcess); } // Restore our current-directory and free up any strings. // SetCurrentDirectory(lpszOldDir); genGFree(lpszOldDir, genGSize(lpszOldDir)); } genGFree(lpszCmd, cbSize); } } return bSuccess; } /*****************************************************************************\ * cab_get_modtime * * Returns the modified-time of the CAB file. This can be used to determine * whether this is out of date with other files. * \*****************************************************************************/ BOOL cab_get_modtime( LPTSTR lpszOutPath, LPFILETIME lpft) { HANDLE hFile; BOOL bRet = FALSE; // Open the file for reading. // hFile = gen_OpenFileRead(lpszOutPath); // If the file exists and was opened, go get the time. // if (hFile && (hFile != INVALID_HANDLE_VALUE)) { bRet = GetFileTime(hFile, NULL, NULL, lpft); CloseHandle(hFile); } return bRet; } /*****************************************************************************\ * cab_get_drvname (Local Routine) * * This routine attempts to open the printer, and retrieve the printer * driver-name associated with the FriendlyName. This returns to pointers * to a driver and a share-name. * \*****************************************************************************/ BOOL cab_get_drvname( LPCTSTR lpszFriendlyName, LPTSTR* lpszDrvName, LPTSTR* lpszShrName, DWORD idxPlt ) { HANDLE hPrinter; LPPRINTER_INFO_2 lppi; DWORD cbBuf; DWORD cbNeed; BOOL bRet = FALSE; // Initialize the pointers for the fail-case. // *lpszShrName = NULL; *lpszDrvName = NULL; // Open the printer and use the handle to query the printer for // the driver-name. // if (OpenPrinter((LPTSTR)lpszFriendlyName, &hPrinter, NULL)) { // First let's see how big our buffer will need to // be in order to hold the PRINTER_INFO_2. // cbBuf = 0; GetPrinter(hPrinter, PRT_LEV_2, NULL, 0, &cbBuf); // Allocate storage for holding the print-info structure. // if (cbBuf && (lppi = (LPPRINTER_INFO_2)genGAlloc(cbBuf))) { if (GetPrinter(hPrinter, PRT_LEV_2, (LPBYTE)lppi, cbBuf, &cbNeed)) { //If this is a Win9X client make sure the driver name is correct if ( genIsWin9X(idxPlt) ) { // Call get printer driver to get the actual driver name for Win9X DWORD rc, cbNeeded; GetPrinterDriver( hPrinter, (LPTSTR) genStrCliEnvironment(idxPlt), 3, NULL, 0, &cbNeeded); rc = GetLastError(); if ( rc == ERROR_INSUFFICIENT_BUFFER ) { LPBYTE pData; DWORD dwSize = cbNeeded; if ( pData = (LPBYTE) genGAlloc(cbNeeded) ) { if ( GetPrinterDriver( hPrinter, (LPTSTR) genStrCliEnvironment(idxPlt), 3, pData, dwSize, &cbNeeded) ) { PDRIVER_INFO_3 pDriver = (PDRIVER_INFO_3) pData; *lpszDrvName = genGAllocStr(pDriver->pName); } genGFree( pData, dwSize ); } } else *lpszDrvName = genGAllocStr(lppi->pDriverName); } else *lpszDrvName = genGAllocStr(lppi->pDriverName); if ( *lpszDrvName ) { if (*lpszShrName = genGAllocStr(lppi->pShareName)) { bRet = TRUE; } else { genGFree(*lpszDrvName, genGSize(*lpszDrvName)); *lpszDrvName = NULL; } } } genGFree(lppi, cbBuf); } ClosePrinter(hPrinter); } return bRet; } /*****************************************************************************\ * cab_get_dstname * * Returns the then name of the destination-files. The name built * incorporates the platform/version so that it is not duplicated with * any other files. * * .webpnp * * i.e. AXP2 - NT Alpha 4.0 * A863 - NT x86 5.0 * AXP3 - NT Alpha 5.0 * W9X0 - Win9X * \*****************************************************************************/ LPTSTR cab_get_dstname( DWORD idxPlt, DWORD idxVer, LPCTSTR lpszShr) { int cch; int cchBuf; LPCTSTR lpszPlt; LPTSTR lpszName = NULL; // Get the platform requested for the client. // if (lpszPlt = genStrCliCab(idxPlt)) { cchBuf = lstrlen(lpszPlt) + MIN_CAB_BUFFER; // Build the cabname according to platform and version. // if (lpszName = (LPTSTR)genGAlloc(cchBuf * sizeof(TCHAR))) { cch = wsprintf(lpszName, g_szCabName, genChkSum(lpszShr), lpszPlt, idxVer); SPLASSERT((cch < cchBuf)); } } return lpszName; } /*****************************************************************************\ * cab_get_name * * Returns the name of the returned-file. * \*****************************************************************************/ VOID cab_get_name( LPCTSTR lpszDstName, LPCTSTR lpszShrName, LPTSTR lpszName) { DWORD cch; cch = wsprintf(lpszName, TEXT("/printers/%s/%s%s"), g_szPrtCabs, lpszDstName, g_szDotIpp); SPLASSERT((cch < MAX_PATH)); return; } /*****************************************************************************\ * cab_get_webdir * * Returns the virtual-root-directory where the cab-files are to be generated * and stored. * \*****************************************************************************/ LPTSTR cab_get_webdir(VOID) { BOOL bRet = FALSE; HRESULT hr; // com error status METADATA_HANDLE hMeta = NULL; // handle to metabase CComPtr pIMeta; // ATL smart ptr DWORD dwMDRequiredDataLen; METADATA_RECORD mr; HANDLE hToken = NULL; LPTSTR lpszDir = NULL; TCHAR szBuf[MAX_CAB_BUFFER]; // Need to revert to our service credential to be able to read IIS Metabase. hToken = RevertToPrinterSelf(); // Create a instance of the metabase object hr=::CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, IID_IMSAdminBase, (void **)&pIMeta); if( SUCCEEDED( hr )) { // open key to ROOT on website #1 (default) hr = pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE, g_szMetabasePath, METADATA_PERMISSION_READ, CAB_TIMEOUT, &hMeta); if( SUCCEEDED( hr )) { mr.dwMDIdentifier = MD_VR_PATH; mr.dwMDAttributes = 0; mr.dwMDUserType = IIS_MD_UT_FILE; mr.dwMDDataType = STRING_METADATA; mr.dwMDDataLen = sizeof(szBuf); mr.pbMDData = reinterpret_cast(szBuf); hr = pIMeta->GetData( hMeta, L"", &mr, &dwMDRequiredDataLen ); pIMeta->CloseKey( hMeta ); if( SUCCEEDED( hr )) { lpszDir = genBuildFileName(szBuf, g_szPrtCabs, NULL); } } } if (hToken) { ImpersonatePrinterClient(hToken); } return lpszDir; } /*****************************************************************************\ * cab_get_dstpath * * Returns a directory string where the cabinet files are to be generated. * \*****************************************************************************/ LPTSTR cab_get_dstpath(VOID) { HANDLE hDir; LPTSTR lpszDir; // First, we need to find the virtual-directory-root the cab files // are located. // if (lpszDir = cab_get_webdir()) { // Make sure the directory exists, or we can create it. // hDir = gen_OpenDirectory(lpszDir); if (hDir && (hDir != INVALID_HANDLE_VALUE)) { CloseHandle(hDir); } else { if (CreateDirectory(lpszDir, NULL) == FALSE) { genGFree(lpszDir, genGSize(lpszDir)); lpszDir = NULL; } } } return lpszDir; } /*****************************************************************************\ * GenerateCAB * * Main entry-point for generating the CAB file. * * Parameters * ---------- * lpszFriendlyName - Name of printer (shared-name). * lpszPortName - Name of output port. * dwCliInfo - Client platform/architecture/version information. * lpszOutName - This is returned to the caller specifying the output. * \*****************************************************************************/ DWORD GenerateCAB( LPCTSTR lpszFriendlyName, LPCTSTR lpszPortName, DWORD dwCliInfo, LPTSTR lpszOutName, BOOL bSecure) { INFGENPARM infParm; HANDLE hToken; HANDLE hsed; HANDLE hinf; FILETIME ftSED; FILETIME ftCAB; DWORD idxVer; DWORD idxPlt; BOOL bSed; BOOL bCab; LPTSTR lpszFullName; LPTSTR lpszDrvName = NULL; LPTSTR lpszShrName = NULL; LPTSTR lpszDstName = NULL; LPTSTR lpszDstPath = NULL; LPTSTR lpszFrnName = NULL; LPCTSTR lpszSedFile; DWORD dwRet = HSE_STATUS_ERROR; DWORD dwFailure = ERROR_SUCCESS; // Initialize the security-token so that we will have // max-privileges during the file-creation. // if ((hToken = cab_SetClientSecurity()) == NULL) { DBGMSG(DBG_ERROR, ("GenerateCab : Access Denied")); goto RetCabDone; } // Get the platform and version of the client (map to an index // into tables). Validate the indexes to assure the information // is valid. // idxPlt = genIdxCliPlatform(dwCliInfo); idxVer = genIdxCliVersion(dwCliInfo); #ifdef WIN95TST // WORK : ChrisWil if (idxPlt == IDX_W9X) { lstrcpy(lpszOutName, TEXT("/hplaserj/cab_w9x0.webpnp")); return HSE_STATUS_SUCCESS; } #endif if ((idxPlt == IDX_UNKNOWN) || (idxVer == IDX_UNKNOWN)) { dwFailure = ERROR_BAD_ENVIRONMENT; DBGMSG(DBG_ERROR, ("GenerateCab : Unsupported client architecture/version")); goto RetCabDone; } // Get the directory where the cab-files will be placed. // if ((lpszDstPath = cab_get_dstpath()) == NULL) goto RetCabDone; // Build a cluster-capable friendly-name. // if ((lpszFrnName = genFrnName(lpszFriendlyName)) == NULL) goto RetCabDone; // Get the driver information about the friendly-name. // if (cab_get_drvname(lpszFrnName, &lpszDrvName, &lpszShrName, idxPlt) == FALSE) goto RetCabDone; // Get the destination-name. // if ((lpszDstName = cab_get_dstname(idxPlt, idxVer, lpszShrName)) == NULL) goto RetCabDone; // Fill in the inf-input-parameters. These values should be // validated at this point. // infParm.lpszFriendlyName = lpszFrnName; infParm.lpszShareName = lpszShrName; infParm.lpszPortName = lpszPortName; infParm.idxPlt = idxPlt; infParm.idxVer = idxVer; infParm.dwCliInfo = dwCliInfo; infParm.lpszDrvName = lpszDrvName; infParm.lpszDstName = lpszDstName; infParm.lpszDstPath = lpszDstPath; // Grab the critical-section while we deal with generating // the files for the driver-install. // EnterCABCrit(); // Call to have the INF/CDF files generated. If // all goes well, then the SED file can be generated // using the INF as input. // if (hinf = infCreate(&infParm)) { // We created an INF object. Now process the INF if ( infProcess(hinf) ) { // Got good INF so now work on CDF if (hsed = cdfCreate(hinf, bSecure)) { // We created a CDF object. Now process the CDF if ( cdfProcess(hsed) ) { // Allocate a string representing the full-path-name of // the generated executable. // lpszFullName = genBuildFileName(lpszDstPath, lpszDstName, g_szDotIpp); if (lpszFullName) { // Get the name of the directive file that we'll be using // to lauch the iexpress-package with. Do not delete this // pointer as it is handled by the SED object. // if (lpszSedFile = cdfGetName(hsed)) { // Retrieve modified dates so that we can determine whether // to generate a new-CAB or return an existing one. If the // calls return FALSE, the we can assume a file doesn't // exist, so we'll generate an new one anyway. // bSed = cdfGetModTime(hsed, &ftSED); bCab = cab_get_modtime(lpszFullName, &ftCAB); // Get the name of the Cab-File that will be // generated (or returned). This is relative to the // wwwroot path and is in the form /share/printer. // cab_get_name(lpszDstName, lpszShrName, lpszOutName); // If the bCabMod is TRUE (meaning we have a CAB), and the // SED is not newer than the CAB, then we really only need // to return the existing CAB. Otherwise, some files // must have changed. // if ((bSed && bCab) && (CompareFileTime(&ftSED, &ftCAB) <= 0)) { goto RetCabName; } else { if (cab_iexpress_exec(lpszDstPath, lpszDstName, lpszSedFile)) { RetCabName: dwRet = HSE_STATUS_SUCCESS; } else dwFailure = ERROR_CANNOT_MAKE; } } else // If cdfGetName dwFailure = GetLastError(); genGFree(lpszFullName, genGSize(lpszFullName)); } else // If lpszFullName dwFailure = GetLastError(); } else // If cdfProcess() dwFailure = cdfGetError(hsed); // Get saved error cdfCleanUpSourceFiles(hinf); // Even if cdfProcess fails it might have // partialy generated some temporary files cdfDestroy(hsed); } else // If cdfCreate dwFailure = GetLastError(); } else // If infProcess() dwFailure = infGetError(hinf); // Get saved error infDestroy(hinf); } else // If infCreate dwFailure = GetLastError(); // Cleanup the files generated during processing. // cab_cleanup_files(lpszDstPath); RetCabDone: // If something failed but we don't know what yet.. Get the error if ( (dwRet == HSE_STATUS_ERROR) && ( dwFailure == ERROR_SUCCESS ) ) dwFailure = GetLastError(); // Free our strings allocated through this scope. // if (lpszFrnName) genGFree(lpszFrnName, genGSize(lpszFrnName)); if (lpszDrvName) genGFree(lpszDrvName, genGSize(lpszDrvName)); if (lpszDstName) genGFree(lpszDstName, genGSize(lpszDstName)); if (lpszShrName) genGFree(lpszShrName, genGSize(lpszShrName)); if (lpszDstPath) genGFree(lpszDstPath, genGSize(lpszDstPath)); if (hToken) cab_ResetClientSecurity(hToken); LeaveCABCrit(); if (dwFailure != ERROR_SUCCESS) SetLastError(dwFailure); return dwRet; }