//
// NicEnum.cpp
//
//		NIC enumeration code, taken from JetNet (hardware group) and repurposed
//		for the Home Networking Wizard.
//
// History:
//
//		 2/02/1999  KenSh     Created for JetNet
//		 9/28/1999  KenSh     Repurposed for Home Networking Wizard
//

#include "stdafx.h"
#include "NetConn.h"
#include "nconnwrap.h"
#include "TheApp.h"


// Local functions
//
HRESULT WINAPI DetectHardwareEx(const NETADAPTER* pAdapter);
BOOL WINAPI IsNetAdapterEnabled(LPCSTR pszEnumKey);


// EnumNetAdapters (public)
//
//		Enumerates all network adapters installed on the system, allocates a structure
//		big enough to hold the information, and returns the number of adapters found.
//		Use NetConnFree() to free the allocated memory.
//
// History:
//
//		 3/15/1999  KenSh     Created
//		 3/25/1999  KenSh     Added code to get Enum key for each adapter
//		 9/29/1999  KenSh     Changed JetNetAlloc to NetConnAlloc
//
int WINAPI EnumNetAdapters(NETADAPTER** pprgNetAdapters)
{
	CRegistry reg(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Services\\Class\\Net"), KEY_READ, FALSE);

	DWORD cAdapters = 0;
	DWORD iKey;

	RegQueryInfoKey(reg.m_hKey, NULL, NULL, NULL, &cAdapters, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

	NETADAPTER* prgNetAdapters = (NETADAPTER*)NetConnAlloc(sizeof(NETADAPTER) * cAdapters);
	if (prgNetAdapters == NULL)
    {
        cAdapters = 0;
		goto done;
    }

	ZeroMemory(prgNetAdapters, sizeof(NETADAPTER) * cAdapters);

	for (iKey = 0; iKey < cAdapters; iKey++)
	{
		NETADAPTER* pAdapter = &prgNetAdapters[iKey];
		pAdapter->bError = NICERR_NONE;
		pAdapter->bWarning = NICWARN_NONE;
		pAdapter->bNetSubType = SUBTYPE_NONE;

		lstrcpy(pAdapter->szClassKey, _T("Net\\"));
		static const int cchNet = _countof(_T("Net\\")) - 1;
		DWORD cbPnpID = _countof(pAdapter->szClassKey) - cchNet;
		if (ERROR_SUCCESS != RegEnumKeyEx(reg.m_hKey, iKey, pAdapter->szClassKey + cchNet, &cbPnpID, NULL, NULL, NULL, NULL))
		{
			pAdapter->bError = NICERR_BANGED;
			continue;
		}

		CRegistry reg2;
		if (!reg2.OpenKey(reg.m_hKey, pAdapter->szClassKey + cchNet, KEY_READ))
		{
			pAdapter->bError = NICERR_BANGED;
			continue;
		}

		// VERIFIED: Win95 gold, Win98 gold
		reg2.QueryStringValue(_T("DriverDesc"), pAdapter->szDisplayName, _countof(pAdapter->szDisplayName));

		CRegistry reg3;
		if (!reg3.OpenKey(reg2.m_hKey, _T("Ndi"), KEY_READ))
		{
			pAdapter->bError = NICERR_BANGED;
			continue;
		}

		if (reg2.QueryStringValue(_T("DisableWarning"), NULL, NULL))
		{
			pAdapter->bWarning = NICWARN_WARNING;
		}

		// VERIFIED: Win95 gold, Win98 gold
		reg2.QueryStringValue(_T("InfPath"), pAdapter->szInfFileName, _countof(pAdapter->szInfFileName));

		// VERIFIED: Win95 gold, Win98 gold
		reg3.QueryStringValue(_T("DeviceId"), pAdapter->szDeviceID, _countof(pAdapter->szDeviceID));

		// Get the name of the driver provider, not the manufacturer
		// We will replace with actual mfr name, if any, when we open the enum key
		reg2.QueryStringValue(_T("ProviderName"), pAdapter->szManufacturer, _countof(pAdapter->szManufacturer));

		// Check for supported interfaces to determine the network type
		CRegistry reg4;
		TCHAR szLower[60];
		szLower[0] = _T('\0');
		if (reg4.OpenKey(reg3.m_hKey, _T("Interfaces"), KEY_READ))
		{
			// REVIEW: should we check LowerRange instead?
			reg4.QueryStringValue(_T("Lower"), szLower, _countof(szLower));
		}

		// Figure out the network adapter type (NIC, Dial-Up, etc.)
		// Default is NETTYPE_LAN (which is automatically set since it's 0)
		if (strstr(szLower, _T("vcomm")))
		{
			pAdapter->bNetType = NETTYPE_DIALUP;
		}
		else if (strstr(szLower, _T("pptp")))
		{
			pAdapter->bNetType = NETTYPE_PPTP;
		}
        else if (strstr(szLower, _T("isdn")))
        {
            pAdapter->bNetType = NETTYPE_ISDN;
        }
        else if (strstr(szLower, _T("NabtsIp")) || strstr(szLower, _T("nabtsip")))
        {
            pAdapter->bNetType = NETTYPE_TV;
			pAdapter->bNicType = NIC_VIRTUAL;
        }
		else
		{
			TCHAR szBuf[80];

			// Check for IrDA adapter
			// VERIFIED: Win98 OSR1
			if (reg3.QueryStringValue(_T("NdiInstaller"), szBuf, _countof(szBuf)))
			{
				LPTSTR pchComma = strchr(szBuf, ',');
				if (pchComma != NULL)
				{
					*pchComma = _T('\0');
					if (!lstrcmpi(szBuf, _T("ir_ndi.dll")))
					{
						pAdapter->bNetType = NETTYPE_IRDA;
					}
				}
			}
		}

		// Determine if card is ISA, PCI, PCMCIA, etc.
		if (pAdapter->szDeviceID[0] == _T('*'))
		{
			if (strstr(szLower, _T("ethernet")))
			{
				if (0 == memcmp(pAdapter->szDeviceID, _T("*AOL"), 4))
				{
					pAdapter->bNicType = NIC_VIRTUAL;
					pAdapter->bNetSubType = SUBTYPE_AOL;
				}
				else
				{
					pAdapter->bNicType = NIC_UNKNOWN;
				}
			}
			else
			{
				pAdapter->bNicType = NIC_VIRTUAL;
			}
		}
		else if (0 == memcmp(pAdapter->szDeviceID, _T("PCMCIA\\"), _lengthof("PCMCIA\\")))
		{
			pAdapter->bNicType = NIC_PCMCIA;
		}
		else if (0 == memcmp(pAdapter->szDeviceID, _T("PCI\\"), _lengthof("PCI\\")))
		{
			pAdapter->bNicType = NIC_PCI;
		}
		else if (0 == memcmp(pAdapter->szDeviceID, _T("ISAPNP\\"), _lengthof("ISAPNP\\")))
		{
			pAdapter->bNicType = NIC_ISA;
		}
		else if (0 == memcmp(pAdapter->szDeviceID, _T("USB\\"), _lengthof("USB\\")))
		{
			pAdapter->bNicType = NIC_USB;
		}
		else if (0 == memcmp(pAdapter->szDeviceID, _T("LPTENUM\\"), _lengthof("LPTENUM\\")))
		{
			pAdapter->bNicType = NIC_PARALLEL;
		}
		else if (0 == memcmp(pAdapter->szDeviceID, _T("MF\\"), _lengthof("MF\\")))
		{
			pAdapter->bNicType = NIC_MF;
		}
		else if (0 == memcmp(pAdapter->szDeviceID, _T("V1394\\"), _lengthof("V1394\\")))
		{
			pAdapter->bNicType = NIC_1394;
		}
		else if (0 == lstrcmpi(pAdapter->szDeviceID, _T("ICSHARE")))
		{
			pAdapter->bNicType = NIC_VIRTUAL;
			pAdapter->bNetSubType = SUBTYPE_ICS;
		}

		// TODO: remove this code, replace with IcsIsExternalAdapter and IcsIsInternalAdapter
		// Check if this adapter is used by ICS
		{
			pAdapter->bIcsStatus = ICS_NONE;
			LPCSTR pszAdapterNumber = pAdapter->szClassKey + cchNet;

			TCHAR szBuf[10];
			CRegistry regIcs;

			if (regIcs.OpenKey(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Services\\ICSharing\\Settings\\General"), KEY_QUERY_VALUE))
			{
				if (regIcs.QueryStringValue(_T("ExternalAdapterReg"), szBuf, _countof(szBuf)))
				{
					if (0 == lstrcmp(szBuf, pszAdapterNumber))
					{
						pAdapter->bIcsStatus = ICS_EXTERNAL;
					}
				}

				// TODO: allow > 1 internal adapter
				if (regIcs.QueryStringValue(_T("InternalAdapterReg"), szBuf, _countof(szBuf)))
				{
					if (0 == lstrcmp(szBuf, pszAdapterNumber))
					{
						pAdapter->bIcsStatus = ICS_INTERNAL;
					}
				}
			}
		}
	}

	// Snip out any adapters that turned out to be invalid
	cAdapters = iKey;
	if (cAdapters == 0)
	{
		NetConnFree(prgNetAdapters);
		prgNetAdapters = NULL;
		goto done;
	}


	//
	// Walk the registry Enum key to find full enum key for each adapter
	//
	if (reg.OpenKey(HKEY_LOCAL_MACHINE, _T("Enum"), KEY_READ))
	{
		TCHAR szSubKey[MAX_PATH];
		DWORD cbSubKey;
		TCHAR szDevEnumKey[MAX_PATH];
		int cchDevEnumKey1; // length of "PCI\"
		int cchDevEnumKey2; // length of "PCI\VEN_10B7&DEV_9050&SUBSYS_00000000&REV_00\"

		for (DWORD iEnumKey = 0; ; iEnumKey++)
		{
			cbSubKey = _countof(szSubKey);
			if (ERROR_SUCCESS != RegEnumKeyEx(reg.m_hKey, iEnumKey, szSubKey, &cbSubKey, NULL, NULL, NULL, NULL))
				break;

			// Start building DevEnumKey e.g. "PCI\"
			lstrcpy(szDevEnumKey, szSubKey);
			cchDevEnumKey1 = (int)cbSubKey;
			szDevEnumKey[cchDevEnumKey1++] = _T('\\');

			CRegistry reg2;
			if (!reg2.OpenKey(reg.m_hKey, szSubKey, KEY_READ)) // e.g. "Enum\PCI"
				continue;

			for (DWORD iEnumKey2 = 0; ; iEnumKey2++)
			{
				cbSubKey = _countof(szSubKey);
				if (ERROR_SUCCESS != RegEnumKeyEx(reg2.m_hKey, iEnumKey2, szSubKey, &cbSubKey, NULL, NULL, NULL, NULL))
					break;

				// Continue building DevEnumKey e.g. "PCI\VEN_10B7&DEV_9050&SUBSYS_00000000&REV_00\"
				lstrcpy(szDevEnumKey + cchDevEnumKey1, szSubKey);
				cchDevEnumKey2 = cchDevEnumKey1 + (int)cbSubKey;
				szDevEnumKey[cchDevEnumKey2++] = _T('\\');

				CRegistry reg3;
				if (!reg3.OpenKey(reg2.m_hKey, szSubKey, KEY_READ)) // e.g. "Enum\PCI\VEN_10B7&DEV_9050&SUBSYS_00000000&REV_00"
					continue;

				for (DWORD iEnumKey3 = 0; ; iEnumKey3++)
				{
					cbSubKey = _countof(szSubKey);
					if (ERROR_SUCCESS != RegEnumKeyEx(reg3.m_hKey, iEnumKey3, szSubKey, &cbSubKey, NULL, NULL, NULL, NULL))
						break;

					// Finish building DevEnumKey e.g. "PCI\VEN_10B7&DEV_9050&SUBSYS_00000000&REV_00\407000"
					lstrcpy(szDevEnumKey + cchDevEnumKey2, szSubKey);

					CRegistry regLeaf;
					if (!regLeaf.OpenKey(reg3.m_hKey, szSubKey, KEY_READ)) // e.g. "Enum\PCI\VEN_10B7&DEV_9050&SUBSYS_00000000&REV_00\407000"
						continue;

					if (!regLeaf.QueryStringValue(_T("Driver"), szSubKey, _countof(szSubKey)))
						continue;

					//
					// See if the device matches one of our NICs
					//
					for (DWORD iAdapter = 0; iAdapter < cAdapters; iAdapter++)
					{
						NETADAPTER* pAdapter = &prgNetAdapters[iAdapter];
						if (0 != lstrcmpi(szSubKey, pAdapter->szClassKey))
							continue; // doesn't match

						lstrcpy(pAdapter->szEnumKey, _T("Enum\\"));
						lstrcpyn(pAdapter->szEnumKey + 5, szDevEnumKey, _countof(pAdapter->szEnumKey) - 5);

						if (regLeaf.QueryStringValue(_T("Mfg"), szSubKey, _countof(szSubKey)))
							lstrcpyn(pAdapter->szManufacturer, szSubKey, _countof(pAdapter->szManufacturer));

						if (regLeaf.QueryStringValue(_T("DeviceDesc"), szSubKey, _countof(szSubKey)))
						{
							lstrcpyn(pAdapter->szDisplayName, szSubKey, _countof(pAdapter->szDisplayName));
							
							// Detect more special types of adapters here
							if (pAdapter->bNetType == NETTYPE_DIALUP)
							{
								if (strstr(pAdapter->szDisplayName, _T("VPN")) ||
									 strstr(pAdapter->szDisplayName, _T("#2")))
								{
									pAdapter->bNetSubType = SUBTYPE_VPN;
								}
							}
						}
						break;  // found a match, so stop looking
					}
				}
			}
		}
	}

	// For all adapters that we think are present, check to see if they're
	// actually present
	DWORD iAdapter;
	for (iAdapter = 0; iAdapter < cAdapters; iAdapter++)
	{
		NETADAPTER* pAdapter = &prgNetAdapters[iAdapter];
        
        GetNetAdapterDevNode(pAdapter);

		// No enum key -> bad (JetNet bug 1234)
		if (pAdapter->szEnumKey[0] == _T('\0'))
		{
			pAdapter->bError = NICERR_CORRUPT;
		}

		// REVIEW: could still check if "broken" adapters are present
		if (pAdapter->bNicType != NIC_VIRTUAL && pAdapter->bError == NICERR_NONE)
		{
			HRESULT hrDetect = DetectHardwareEx(pAdapter);

			if (hrDetect == S_FALSE)
			{
				pAdapter->bError = NICERR_MISSING;
			}
			else if (hrDetect == S_OK)
			{
				// Is the adapter disabled?
				if (!IsNetAdapterEnabled(pAdapter->szEnumKey))
				{
					pAdapter->bError = NICERR_DISABLED;
				}
				else if (IsNetAdapterBroken(pAdapter))
				{
					pAdapter->bError = NICERR_BANGED;
				}
			}
		}
    }

done:
    *pprgNetAdapters = prgNetAdapters;
    return (int)cAdapters;
}

// Gets the name of the VxD from the registry, e.g. "3c19250.sys".
// Returns S_OK if the name was retrieved.
// Returns E_FAIL if the name was not retrieved, and sets pszBuf to an empty string.
HRESULT WINAPI GetNetAdapterDeviceVxDs(const NETADAPTER* pAdapter, LPSTR pszBuf, int cchBuf)
{
    CRegistry reg(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Services\\Class"), KEY_READ, FALSE);
    if (reg.OpenSubKey(pAdapter->szClassKey, KEY_READ))
    {
        if (reg.QueryStringValue(_T("DeviceVxDs"), pszBuf, cchBuf))
        {
            return S_OK;
        }
    }

    *pszBuf = '\0';
    return E_FAIL;
}

// Returns S_OK if the NIC is present, S_FALSE if not, or an error code if the test failed
HRESULT WINAPI DetectHardware(LPCSTR pszDeviceID)
{
    // Just thunk down to the 16-bit version which uses DiGetClassDevs
    HRESULT hr = FindClassDev16(NULL, _T("Net"), pszDeviceID);
    return hr;
}

HRESULT WINAPI DetectHardwareEx(const NETADAPTER* pAdapter)
{
    // Hack: always assume IRDA adapters are present, since HW detection doesn't
    // work on them -ks 8/8/99
    // TODO: see if this is fixed in the updated DetectHardware()  -ks 9/28/1999
//    if (pAdapter->bNetType == NETTYPE_IRDA)
//        return S_OK;

    // Hack: always assume unknown NIC types are present, since HW detection
    // doesn't work on them (JetNet bug 1264 - Intel AnyPoint Parallel Port Adapter)
    // TODO: see if this is fixed in the updated DetectHardware()  -ks 9/28/1999
//    if (pAdapter->bNicType == NIC_UNKNOWN)
//        return S_OK;

    // Hack: work around Millennium bug 123237, which says that hardware detection
    // fails for NICs using the Dc21x4.sys driver. I never got a chance to track
    // down the cause of the failure, so I'm cheating instead.  -ks 1/13/2000
    TCHAR szBuf[100];
    GetNetAdapterDeviceVxDs(pAdapter, szBuf, _countof(szBuf));
    if (0 == lstrcmpi(szBuf, _T("dc21x4.sys")))
        return S_OK;

    return DetectHardware(pAdapter->szDeviceID);
}

BOOL OpenConfigKey(CRegistry& reg, LPCSTR pszSubKey, REGSAM dwAccess)
{
    if (reg.OpenKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Control\\IDConfigDB", KEY_QUERY_VALUE))
    {
        TCHAR szConfigNumber[20];
        if (reg.QueryStringValue("CurrentConfig", szConfigNumber, _countof(szConfigNumber)))
        {
            TCHAR szRegKey[300];
            wsprintf(szRegKey, "Config\\%s\\%s", szConfigNumber, pszSubKey);
            if (reg.OpenKey(HKEY_LOCAL_MACHINE, szRegKey, dwAccess))
            {
                return TRUE;
            }
        }
    }

    return FALSE;
}

BOOL WINAPI IsNetAdapterEnabled(LPCSTR pszEnumKey)
{
    BOOL bEnabled = TRUE;  // assume enabled if reg keys are missing

    CRegistry reg;
    if (OpenConfigKey(reg, pszEnumKey, KEY_QUERY_VALUE))
    {
        DWORD dwDisabled;
        if (reg.QueryDwordValue("CSConfigFlags", &dwDisabled))
        {
            bEnabled = (dwDisabled == 0);
        }
    }

    return bEnabled;
}