/*** findoem.cpp - OEM detection interface
 *
 *  Copyright (c) 1998-1999 Microsoft Corporation.  All Rights Reserved.
 *
 *  Author:     Yan Leshinsky (YanL)
 *  Created     10/08/98
 *
 *  MODIFICATION HISTORY
 */

//#define _WIN32_WINNT 0x0400
#include <comdef.h>
#include <windows.h>
#include <ole2.h>
#include <stdlib.h>
#include <wbemcli.h>
#include <tchar.h>
#include <atlconv.h>

#include <wustl.h>

#include "cdmlibp.h"

// Smart pointers
_COM_SMARTPTR_TYPEDEF(IWbemLocator, __uuidof(IWbemLocator));
_COM_SMARTPTR_TYPEDEF(IWbemServices, __uuidof(IWbemServices));
_COM_SMARTPTR_TYPEDEF(IEnumWbemClassObject, __uuidof(IEnumWbemClassObject));
_COM_SMARTPTR_TYPEDEF(IWbemClassObject, __uuidof(IWbemClassObject));

// hardcodes - not defined in any header
const CLSID CLSID_WbemLocator = {0x4590f811,0x1d3a,0x11d0,{0x89,0x1f,0x00,0xaa,0x00,0x4b,0x2e,0x24}};

#include "try_catch.h"

#include "findoem.h"

#define BYTEOF(d,i)	(((BYTE *)&(d))[i])

/*** That will let me to include file into queryoem and avoid having additional dll
 */
extern HMODULE GetModule();

/*** Local function prototypes
 */
static void UseOeminfoIni(POEMINFO pOemInfo);
static void UseWBEM(POEMINFO pOemInfo);
static bool ReadFromReg(POEMINFO pOemInfo);
static void SaveToReg(POEMINFO pOemInfo);

/*** Registry access
 */
static const TCHAR REGSTR_KEY_OEMINFO[]		= _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\OemInfo");
static const TCHAR REGSTR_VAL_MASK[]			= _T("Mask");
static const TCHAR REGSTR_VAL_ACPIOEM[]		= _T("AcpiOem");
static const TCHAR REGSTR_VAL_ACPIPRODUCT[]	= _T("AcpiProduct");
static const TCHAR REGSTR_VAL_SMBOEM[]		= _T("SmbOem");
static const TCHAR REGSTR_VAL_SMBPRODUCT[]	= _T("SmbProduct");
static const TCHAR REGSTR_VAL_PNPOEMID[]		= _T("PnpOemId");
static const TCHAR REGSTR_VAL_INIOEM[]		= _T("IniOem");
static const TCHAR REGSTR_VAL_WBEMOEM[]		= _T("WbemOem");
static const TCHAR REGSTR_VAL_WBEMPRODUCT[]	= _T("WbemProduct");
static const TCHAR REGSTR_VAL_OEMINFO_VER[]	= _T("OemInfoVersion");	// used to determine if we need to nuke old values


//
//	Increment REG_CURRENT_OEM_VER whenever you need to force override of
//	old values written to the OemInfo key. Doesn't need to change for each
//	new control version.
//
#define REG_CURRENT_OEM_VER	1

/*** GetMachinePnPID - Find IDs corresponding to Make and Model code
 *
 *  ENTRY
 *      PSZ szTable
 *		Table format is 
 *
 *		|##| ... |##|00|##|##|##|##|
 *		|           |  |           |
 *		  OEM Model  \0    PnP ID   
 *
 *		|00|00|00|00|
 *		|           |
 *           00000
 *
 *  EXIT
 *      PnP ID  or 0
 */
DWORD GetMachinePnPID(PBYTE pbTable)
{
	USES_CONVERSION;

	LPTSTR sz = (LPTSTR)DetectMachineID();
	LPSTR szMnM = T2A(sz);
	if (NULL == szMnM)
	{
		return 0;
	}
	while (*pbTable)
	{
		PDWORD pId = (PDWORD)(pbTable + strlen((LPSTR)pbTable) + 1); // Skip code position on IDs
		if (0 == strcmp((LPSTR)pbTable, szMnM))
			return *pId;
		pbTable = (PBYTE)(pId + 1); // Skip last 0
	}
	return 0;
}

/*** DetectMachineID - Gather all available machine OEM and model information nad return ID
 *
 *  EXIT
 *		returns ID
 */
LPCTSTR DetectMachineID(bool fAlwaysDetectAndDontSave /*= false*/)
{
	OEMINFO oi;
	GetOemInfo(&oi, fAlwaysDetectAndDontSave);
	return MakeAndModel(&oi);
}


/*** MakeAndModel - Return ID for a machine
 *
 *  ENTRY
 *      POEMINFO pOemInfo
 *
 *  EXIT
 *		returns ID
 */
LPCTSTR MakeAndModel(POEMINFO pOemInfo)
{
	static TCHAR szMnM[256];

#ifdef _WUV3TEST
	auto_hkey hkey;
	if (NO_ERROR == RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGKEY_WUV3TEST, 0, KEY_READ, &hkey)) {
		DWORD dwLangID = 0;
		DWORD dwSize = sizeof(szMnM);
		if (NO_ERROR == RegQueryValueEx(hkey, _T("MachineID"), 0, 0, (LPBYTE)szMnM, &dwSize))
		{
		return szMnM;
		}
	}
#endif

	szMnM[0] = 0;
	if (pOemInfo->dwMask & OEMINFO_WBEM_PRESENT)
	{
		lstrcpy(szMnM, pOemInfo->szWbemOem);
		lstrcat(szMnM, _T(" "));
		lstrcat(szMnM, pOemInfo->szWbemProduct);
		lstrcat(szMnM, _T(";"));
	}
	else if (pOemInfo->dwMask & OEMINFO_INI_PRESENT)
	{
		lstrcat(szMnM, pOemInfo->szIniOem);
		lstrcat(szMnM, _T(";"));
	}
	return szMnM;
}


/*** GetOemInfo - Gather all available machine OEM and model information
 *
 *  ENTRY
 *      POEMINFO pOemInfo
 *
 *  EXIT
 *      POEMINFO pOemInfo
 *		All fields that aren't available will be filled with 0
 *      returns NULL
 */
void GetOemInfo(POEMINFO pOemInfo, bool fAlwaysDetectAndDontSave /*= false*/)
{
	// Worst case:
	memset(pOemInfo, 0, sizeof(OEMINFO)); 

	// Do detection if necessary or requested
	if (fAlwaysDetectAndDontSave || ! ReadFromReg(pOemInfo))
	{
		UseWBEM(pOemInfo);
		UseOeminfoIni(pOemInfo);
		// Save info to the registry
		if (!fAlwaysDetectAndDontSave)
		{
			SaveToReg(pOemInfo);
		}
	}
}

/***LP	IsValidStringID - check if string ID is valid
 *
 *  ENTRY
 *	pszID pOemInfo-> Pnp ID string
 *
 *  EXIT-SUCCESS
 *			returns TRUE
 *  EXIT-FAILURE
 *			returns FALSE
 */

BOOL IsValidStringID(PSZ pszID)
{

    return (strlen(pszID) == 7) &&
		isupper(pszID[0]) && isupper(pszID[1]) && isupper(pszID[2]) &&
		isxdigit(pszID[3]) && isxdigit(pszID[4]) && isxdigit(pszID[5]) && isxdigit(pszID[6]);
}	//IsValidStringID

/***LP  NumericID - convert string ID to numeric ID
 *
 *  ENTRY
 *      psz pOemInfo-> PnP ID string
 *
 *  EXIT
 *      returns numeric ID
 */

DWORD NumericID(PSZ psz)
{
    DWORD dwID;
    WORD wVenID;
	int i;
    for (i = 0, wVenID = 0; i < 3; ++i)
    {
		wVenID <<= 5;
		wVenID |= (psz[i] - 0x40) & 0x1f;
    }
    dwID = strtoul(&psz[3], NULL, 16);
    dwID = ((dwID & 0x00ff) << 8) | ((dwID & 0xff00) >> 8);
    dwID <<= 16;
    dwID |= (wVenID & 0x00ff) << 8;
    dwID |= (wVenID & 0xff00) >> 8;
    return dwID;
}       //NumericID

/***LP  StringID - convert numeric ID to string ID
 *
 *  ENTRY
 *      dwID - numeric PnP ID
 *
 *  EXIT
 *      returns string ID
 */

PSZ StringID(DWORD dwID)
{
	static char szID[8];
    WORD wVenID;
    int i;

	wVenID = (WORD)(((dwID & 0x00ff) << 8) | ((dwID & 0xff00) >> 8));
	wVenID <<= 1;

	for (i = 0; i < 3; ++i)
	{
		szID[i] = (char)(((wVenID & 0xf800) >> 11) + 0x40);
		wVenID <<= 5;
	}
	wVenID = HIWORD(dwID);
	wVenID = (WORD)(((wVenID & 0x00ff) << 8) | ((wVenID & 0xff00) >> 8));
	for (i = 6; i > 2; --i)
	{
		szID[i] = (char)(wVenID & 0x000F);
		if(szID[i] > 9)
		{
			szID[i] += 0x37; // 'A' - 0xA	for digits A to F
		}
		else
		{
			szID[i] += 0x30; // '0'			for digits 0 to 9
		}
		wVenID >>= 4;
	}
    return szID;
} //StringID


/*** UseOeminfoIni - get OemInfo from OEMINFO.INI
 *
 *  ENTRY
 *      POEMINFO pOemInfo
 *
 *  EXIT
 *      POEMINFO pOemInfo
 *		All fields that aren't available will be filled with 0
 *      returns NULL
 */
void UseOeminfoIni(POEMINFO pOemInfo)
{
	USES_CONVERSION;

	static const TCHAR szFile[]		= _T("OEMINFO.INI");
	static const TCHAR szSection[]	= _T("General");
	static const TCHAR szKey[]		= _T("Manufacturer");
	TCHAR szPath[MAX_PATH];
	// WU Bug# 11921 -- OEMINFO.INI is in system directory not Windows directory 
	if (GetSystemDirectory(szPath, sizeOfArray(szPath)) > 0)
	{
		// check for "c:\"
		if (szPath[lstrlen(szPath)-1] != '\\')
		{
			lstrcat(szPath, _T("\\"));
		}
		lstrcat(szPath, szFile);
		GetPrivateProfileString(szSection, szKey, _T(""), 
			pOemInfo->szIniOem, sizeOfArray(pOemInfo->szIniOem), szPath);
		if (lstrlen(pOemInfo->szIniOem))
		{
			pOemInfo->dwMask |= OEMINFO_INI_PRESENT;
		}
   }
}

/*** UseWBEM - Get info through WBEM access
 *
 *  ENTRY
 *      POEMINFO pOemInfo
 *
 *  EXIT
 *      POEMINFO pOemInfo
 *		All fields that aren't available will be filled with 0
 *      returns NULL
 */
void UseWBEM(POEMINFO pOemInfo)
{
	try_block 
	{

		USES_CONVERSION;
		
		// Create Locator
		IWbemLocatorPtr pWbemLocator;
		throw_hr(pWbemLocator.CreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER));
		
		// Get services
		IWbemServicesPtr pWbemServices;
		throw_hr(pWbemLocator->ConnectServer(bstr_t(L"\\\\.\\root\\cimv2"), NULL, NULL, 0L, 0L, NULL, NULL, &pWbemServices));
        throw_hr(CoSetProxyBlanket(pWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
                       RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE));

		// Create enumerator
		IEnumWbemClassObjectPtr pEnum;
		throw_hr(pWbemServices->CreateInstanceEnum(bstr_t(L"Win32_ComputerSystem"), 0, NULL, &pEnum));
        throw_hr(CoSetProxyBlanket(pEnum, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
                       RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE));

		// Get our object now
		ULONG uReturned = 1;
		IWbemClassObjectPtr pObject;
		throw_hr(pEnum->Next(
                6000,           // timeout in two seconds
                1,              // return just one storage device
                &pObject,		// pointer to storage device
                &uReturned));	// number obtained: one or zero

		variant_t var;
		throw_hr(pObject->Get(L"Manufacturer", 0L, &var, NULL, NULL));
		if (VT_BSTR == var.vt)
		{
			lstrcpy(pOemInfo->szWbemOem, W2T(var.bstrVal));
		}
		throw_hr(pObject->Get(L"Model", 0L, &var, NULL, NULL));
		if (VT_BSTR == var.vt) 
		{
			lstrcpy(pOemInfo->szWbemProduct, W2T(var.bstrVal));
		}
		if (0 != lstrlen(pOemInfo->szWbemOem) || 0 != lstrlen(pOemInfo->szWbemProduct))
		{
			pOemInfo->dwMask |= OEMINFO_WBEM_PRESENT;
		}
	} catch_all;
}

/*** ReadFromReg - read OEMINFO from registry
 *
 *  ENTRY
 *      POEMINFO pOemInfo
 *
 *  EXIT
 *      true if info is present
 *		false otherwise
 */
bool ReadFromReg(POEMINFO pOemInfo)
{
	DWORD dwVersion = 0;
	//read registry first
	auto_hkey hkeyOemInfo;
	if	(NO_ERROR == RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_KEY_OEMINFO, 0, KEY_READ, &hkeyOemInfo))
	{
		DWORD dwCount = sizeof(pOemInfo->dwMask);	
		RegQueryValueEx(hkeyOemInfo, REGSTR_VAL_MASK, 0, 0, (LPBYTE)&(pOemInfo->dwMask), &dwCount);
		
		//
		// ***** WU Bug# 11921 *****
		//
		
		//
		//	No bits set requires detection
		//
		if(!pOemInfo->dwMask)
			return false;

		//
		//	If an older version of the detection wrote the OemInfo return false to force detection.
		//	This value is written starting with 1 around August 2000 for the Classic control.
		//
		dwCount = sizeof(dwVersion);
		if (NO_ERROR == RegQueryValueEx(hkeyOemInfo, REGSTR_VAL_OEMINFO_VER, 0, 0, (LPBYTE)&dwVersion, &dwCount))
		{
			if(REG_CURRENT_OEM_VER > dwVersion)
				return false;
		}
		else
		{
			return false;
		}

		//
		// ***** end WU Bug *****
		//

		if (pOemInfo->dwMask & OEMINFO_INI_PRESENT)
		{
			dwCount = sizeof(pOemInfo->szIniOem);	
			RegQueryValueEx(hkeyOemInfo, REGSTR_VAL_INIOEM, 0, 0, (LPBYTE)&(pOemInfo->szIniOem), &dwCount);
		}
		if (pOemInfo->dwMask & OEMINFO_WBEM_PRESENT)
		{
			dwCount = sizeof(pOemInfo->szWbemOem);	
			RegQueryValueEx(hkeyOemInfo, REGSTR_VAL_WBEMOEM, 0, 0, (LPBYTE)&(pOemInfo->szWbemOem), &dwCount);
			dwCount = sizeof(pOemInfo->szWbemProduct);	
			RegQueryValueEx(hkeyOemInfo, REGSTR_VAL_WBEMPRODUCT, 0, 0, (LPBYTE)&(pOemInfo->szWbemProduct), &dwCount);
		}
		return true;
	}
	else
	{
		return false;
	}
}

/*** SaveToReg - Save OEMINFO
 *
 *  ENTRY
 *      POEMINFO pOemInfo
 *
 *  EXIT
 *      none
 */
void SaveToReg(POEMINFO pOemInfo)
{
	DWORD dwDisp;
	DWORD dwVersion = REG_CURRENT_OEM_VER;
	auto_hkey hkeyOemInfo;
	if	(NO_ERROR == RegCreateKeyEx(HKEY_LOCAL_MACHINE, REGSTR_KEY_OEMINFO, 0, NULL, 
		REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &hkeyOemInfo, &dwDisp))
	{
		RegSetValueEx(hkeyOemInfo, REGSTR_VAL_MASK, 0, REG_DWORD, (LPBYTE)&(pOemInfo->dwMask), sizeof(pOemInfo->dwMask));

		//
		//	Write the current version so future controls can check version of detection that wrote this key.
		//	WU RAID # 11921
		//
		RegSetValueEx(hkeyOemInfo, REGSTR_VAL_OEMINFO_VER, 0, REG_DWORD, (LPBYTE)&dwVersion, sizeof(dwVersion));

		if (pOemInfo->dwMask & OEMINFO_INI_PRESENT)
		{
			RegSetValueEx(hkeyOemInfo, REGSTR_VAL_INIOEM, 0, REG_SZ, 
				(LPBYTE)&(pOemInfo->szIniOem), (lstrlen(pOemInfo->szIniOem) + 1) * sizeof(TCHAR));
		}
		if (pOemInfo->dwMask & OEMINFO_WBEM_PRESENT)
		{
			RegSetValueEx(hkeyOemInfo, REGSTR_VAL_WBEMOEM, 0, REG_SZ, 
				(LPBYTE)&(pOemInfo->szWbemOem),(lstrlen(pOemInfo->szWbemOem) + 1) * sizeof(TCHAR));
			RegSetValueEx(hkeyOemInfo, REGSTR_VAL_WBEMPRODUCT, 0, REG_SZ, 
				(LPBYTE)&(pOemInfo->szWbemProduct), (lstrlen(pOemInfo->szWbemProduct) + 1) * sizeof(TCHAR));
		}
	}
}