516 lines
16 KiB
C++
516 lines
16 KiB
C++
#include <winsock2.h>
|
||
#include "IcsMgr.h"
|
||
#include <winbase.h>
|
||
#include <winreg.h>
|
||
#include <tchar.h>
|
||
#include <sensapi.h>
|
||
#include "msobcomm.h"
|
||
// #include "appdefs.h"
|
||
|
||
typedef BOOL (WINAPI * LPFNDLL_ISICSAVAILABLE) ();
|
||
|
||
|
||
static const DWORD ICSLAP_DIAL_STATE = 15; // As per ICS Specification
|
||
static const DWORD ICSLAP_GENERAL_STATUS = 21;
|
||
static CIcsMgr *ptrIcsMgr = NULL;
|
||
static BOOL bIsWinsockInitialized = FALSE;
|
||
|
||
static const WCHAR cszIcsHostIpAddress[] = L"192.168.0.1";
|
||
|
||
extern CObCommunicationManager* gpCommMgr;
|
||
|
||
// based on ICS beacon protocol
|
||
typedef struct _ICS_DIAL_STATE_CB
|
||
{
|
||
ICS_DIAL_STATE state;
|
||
DWORD options;
|
||
} ICS_DIAL_STATE_CB;
|
||
|
||
// used for IsIcsAvailable()
|
||
const static WCHAR cszIcsKey[] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\OOBE\\Ics";
|
||
const static WCHAR cszIcsStatusValueName[] = L"IsIcsAvailable";
|
||
|
||
// used for winsock operations
|
||
static WORD wVersionRequested = MAKEWORD ( 2, 2 );
|
||
static WSADATA SocketData;
|
||
|
||
CIcsMgr::CIcsMgr() : m_hBotThread(0), m_dwBotThreadId(0), m_hDialThread(0), m_dwDialThreadId(0), m_pfnIcsConn(OnIcsConnectionStatus)
|
||
{
|
||
ptrIcsMgr = this;
|
||
if ( !bIsWinsockInitialized )
|
||
{
|
||
if ( !WSAStartup ( wVersionRequested, &SocketData ) )
|
||
{
|
||
bIsWinsockInitialized = TRUE;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
CIcsMgr::~CIcsMgr()
|
||
{
|
||
if ( m_hDialThread ) CloseHandle (m_hDialThread);
|
||
if ( bIsWinsockInitialized )
|
||
{
|
||
//WSACleanup ();
|
||
bIsWinsockInitialized = FALSE;
|
||
}
|
||
ptrIcsMgr = NULL;
|
||
TriggerIcsCallback ( FALSE );
|
||
return;
|
||
|
||
}
|
||
|
||
BOOL CIcsMgr::IsCallbackUsed ()
|
||
{
|
||
return !bReducedCallback;
|
||
}
|
||
|
||
// A server error during ICS is trapped by the ICS manager, instead
|
||
// of the OOBE MSOBMAIN body. This gives the manager a larger sphere
|
||
// of control.
|
||
VOID CIcsMgr::NotifyIcsMgr(UINT msg, WPARAM wparam, LPARAM lparam)
|
||
{
|
||
switch (msg)
|
||
{
|
||
case WM_OBCOMM_ONSERVERERROR:
|
||
{
|
||
// on server error! is the host still available ?
|
||
if ( ! IsDestinationReachable ( cszIcsHostIpAddress, NULL ) )
|
||
{
|
||
// fire event that Home Network is unavailable.
|
||
OnIcsConnectionStatus ( ICS_HOMENET_UNAVAILABLE );
|
||
}
|
||
else
|
||
{ // this will be considered a timeout error.
|
||
OnIcsConnectionStatus ( ICS_TIMEOUT );
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
return;
|
||
}
|
||
|
||
// PACKET READER ------------------
|
||
// Note: Please refer to the ICS Specifications for the packet format. You can
|
||
// consult RLamb@microsoft.com for the documentation.
|
||
//
|
||
// Description: This function listens for UDP packets arriving at the ICS
|
||
// broadcast port. The ICS host sends notifications to the Home Network
|
||
// whenever the Connection status changes at the Shared Connection. The func
|
||
// reads the packet and notifies OOBE by firing a callback function (*lpParam)
|
||
// which notifies OOBE via PostMessage(). A script routine can eventually be
|
||
// executed to handle the notification.
|
||
//
|
||
// An ICS broadcast packet has the following format:
|
||
// |resp:0,bcast:1,id:2-31|cbData:0-31|data(cbData - 8 bytes)|
|
||
// |<---------32 bits---->|<-32 bits->|<---total_length - 8 ------>|
|
||
// |<IE-1>|<IE-2>|<7C><><EFBFBD>..<2E><>|<IE-N>|
|
||
//
|
||
// Each information element (IE) has the following format:
|
||
// | opcode 0-31 | cbIE 0=64 |data(cbIE - 12 bytes)|
|
||
// |<-- 32 bits -->|<-- 64 bits -->|<-- cbIE - 12 bytes |
|
||
//
|
||
DWORD WINAPI IcsDialStatusProc(LPVOID lpParam)
|
||
{
|
||
INT n = 0;
|
||
|
||
u_short usPort = 2869;
|
||
struct sockaddr_in saddr, caddr;
|
||
INT caddr_len = sizeof ( caddr );
|
||
BYTE rgbBuf[300];
|
||
DWORD dwBufSize = sizeof ( rgbBuf );
|
||
LPDWORD pdw = 0;
|
||
BYTE *lpbie = 0;
|
||
BYTE *lpbBound = 0;
|
||
ICS_DIAL_STATE_CB *ptrDial = 0;
|
||
SOCKET s = INVALID_SOCKET;
|
||
PFN_ICS_CONN_CALLBACK pfn_IcsCallback = NULL;
|
||
DWORD dwError = NULL;
|
||
|
||
bIsDialThreadAlive = TRUE;
|
||
|
||
if ( !lpParam )
|
||
{
|
||
bIsDialThreadAlive = FALSE;
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
if ( !bIsWinsockInitialized )
|
||
{
|
||
bIsDialThreadAlive = FALSE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
if ( (s = socket ( AF_INET, SOCK_DGRAM, 0 )) == INVALID_SOCKET )
|
||
{
|
||
bIsDialThreadAlive = FALSE;
|
||
return E_FAIL; // for want of a better return value *BUGBUG*
|
||
// TRACE ( L"SOCKET Error.\t:%d:\n", WSAGetLastError() );
|
||
}
|
||
else
|
||
{
|
||
__try
|
||
{
|
||
memset ( &saddr, 0, sizeof (saddr) );
|
||
saddr.sin_family = AF_INET;
|
||
saddr.sin_addr.S_un.S_addr = htonl ( INADDR_ANY );
|
||
saddr.sin_port = htons ( usPort );
|
||
|
||
if ( bind( s, (struct sockaddr *) &saddr, sizeof(saddr) ) == SOCKET_ERROR )
|
||
{
|
||
// TRACE ( L"Bind error.\n" );
|
||
dwError = WSAGetLastError();
|
||
}
|
||
else
|
||
{
|
||
if (ptrIcsMgr) ptrIcsMgr->RefreshIcsDialStatus();
|
||
for ( ; ; )
|
||
{
|
||
if ( (n = recvfrom ( s, (CHAR*)rgbBuf, dwBufSize, 0, (struct sockaddr *) &caddr, &caddr_len )) == SOCKET_ERROR )
|
||
{
|
||
// TRACE ( L"Socket Error.\n" );
|
||
break;
|
||
}
|
||
lpbBound = rgbBuf+n; // this protects us from illegal packet configurations.
|
||
// TRACE ( L" Something received! Size = %d\n" , n );
|
||
|
||
// checking for BROADCAST packets //
|
||
if ( *(pdw = (LPDWORD) rgbBuf) & 0xC0000000 )
|
||
{
|
||
// This is a broadcast packet! We can parse the packet.
|
||
}
|
||
else
|
||
{
|
||
// non-broadcast packets are ignored.
|
||
continue;
|
||
}
|
||
lpbie = rgbBuf+8;
|
||
|
||
while ( lpbie && ( (lpbie+8) <= lpbBound) )
|
||
{
|
||
if ( *(pdw = ((PDWORD)lpbie)) == ICSLAP_DIAL_STATE )
|
||
{
|
||
// TRACE (L"Dial State Engine. The Datasize is %d\n", pdw[2]-12);
|
||
if ( (lpbie+12+sizeof(ICS_DIAL_STATE_CB)) <= lpbBound )
|
||
{
|
||
ptrDial = (ICS_DIAL_STATE_CB*)(lpbie+12);
|
||
|
||
pfn_IcsCallback = *((PFN_ICS_CONN_CALLBACK*)lpParam);
|
||
|
||
if ( pfn_IcsCallback )
|
||
{
|
||
pfn_IcsCallback ( ptrDial->state );
|
||
}
|
||
// TRACE (L"Dial State = %d\n", ptrDial->state);
|
||
lpbie = 0;
|
||
}
|
||
else
|
||
{
|
||
// packet has illegal data.
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// not the correct ie.
|
||
if ( (lpbie += pdw[2]) >= lpbBound )
|
||
{
|
||
// we traversed the packet without finding the correct ie.
|
||
// TRACE (L"Done.\n");
|
||
lpbie = 0;
|
||
}
|
||
// else we continue the loop.
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
}
|
||
__finally
|
||
{
|
||
|
||
// graceful shutdown of the socket.
|
||
shutdown ( s, SD_BOTH );
|
||
closesocket ( s );
|
||
}
|
||
}
|
||
bIsDialThreadAlive = FALSE;
|
||
|
||
return ERROR_SUCCESS;
|
||
}
|
||
|
||
|
||
|
||
// this is the callback routine that reports ICS connection state information.
|
||
// it relies on both the Beacon protocol and Internet Explorer's error handling
|
||
// (see ONSERVERERROR for details.)
|
||
VOID CALLBACK OnIcsConnectionStatus(ICS_DIAL_STATE dwIcsConnectionStatus)
|
||
{
|
||
|
||
eIcsDialState = dwIcsConnectionStatus;
|
||
|
||
if ( !gpCommMgr ) return;
|
||
|
||
TRACE1(L"ICS Connection Status %d", dwIcsConnectionStatus);
|
||
|
||
// we are not interested in the modem scenario. only ics-broadband is supported.
|
||
if ( (dwIcsConnectionStatus == ICSLAP_CONNECTING) ||
|
||
(dwIcsConnectionStatus == ICSLAP_CONNECTED) ||
|
||
(dwIcsConnectionStatus == ICSLAP_DISCONNECTING) ||
|
||
(dwIcsConnectionStatus == ICSLAP_DISCONNECTED) )
|
||
{
|
||
bIsBroadbandIcsAvailable = FALSE;
|
||
return;
|
||
}
|
||
// indication of ics-broadband
|
||
if (dwIcsConnectionStatus == ICSLAP_PERMANENT)
|
||
bIsBroadbandIcsAvailable = TRUE;
|
||
|
||
// none of the other states will change the bIsBroadbandIcsAvailable value.
|
||
|
||
// if the callback mechanism has been turned off, we will not report
|
||
// connection status to the upper application layer(s).
|
||
if ( bReducedCallback )
|
||
{
|
||
return;
|
||
}
|
||
PostMessage ( gpCommMgr->m_hwndCallBack, WM_OBCOMM_ONICSCONN_STATUS, (WPARAM)0, (LPARAM)dwIcsConnectionStatus);
|
||
}
|
||
|
||
// by turning this ON or OFF ( TRUE / FALSE respectively ), we can control
|
||
// whether or not to inform OOBE of ICS-connection status changes.
|
||
VOID CIcsMgr::TriggerIcsCallback(BOOL bStatus)
|
||
{
|
||
bReducedCallback = !bStatus; // if we want to un-trigger the callback, we go to "sleep" state.
|
||
if ( bStatus )
|
||
{
|
||
RefreshIcsDialStatus();
|
||
}
|
||
}
|
||
|
||
// Obsolete, but retained in case the beacon protocol becomes
|
||
// functional. This function used to call an ICS API to check if ICS was available.
|
||
// this is no longer useful for 2 reasons:
|
||
// 1. We ONLY want one type of ICS (broadband, as opposed to Dial-up)
|
||
// 2. The function does not report ICS availability if the machine it is called in
|
||
// is the ICS HOST itself.
|
||
DWORD IcsEngine(LPVOID lpParam) {
|
||
|
||
// lpParam is ignored.
|
||
|
||
|
||
HINSTANCE hIcsDll = NULL;
|
||
LPFNDLL_ISICSAVAILABLE lpfndll_IsIcsAvailable = NULL;
|
||
BOOL bIsIcsAvailable = FALSE;
|
||
|
||
|
||
HKEY hIcsRegKey = 0;
|
||
LONG lRetVal = 0;
|
||
ICSSTATUS dwIcsStatus = ICS_ENGINE_NOT_COMPLETE;
|
||
DWORD nRet = 0;
|
||
DWORD dwStatus = 0;
|
||
nRet = RegCreateKeyEx ( HKEY_LOCAL_MACHINE,
|
||
cszIcsKey,
|
||
0,
|
||
L"",
|
||
REG_OPTION_NON_VOLATILE,
|
||
KEY_WRITE,
|
||
NULL,
|
||
&hIcsRegKey,
|
||
&dwStatus
|
||
);
|
||
|
||
if (nRet != ERROR_SUCCESS)
|
||
{
|
||
// Registry APIs refuse to create key. No point continuing farther.
|
||
return (nRet = GetLastError());
|
||
}
|
||
__try {
|
||
if ( !(hIcsDll = LoadLibrary(L"ICSAPI32.DLL")) )
|
||
{
|
||
nRet = GetLastError();
|
||
dwIcsStatus = ICS_ENGINE_FAILED;
|
||
__leave;
|
||
}
|
||
if ( !(lpfndll_IsIcsAvailable = (LPFNDLL_ISICSAVAILABLE) GetProcAddress (hIcsDll, "IsIcsAvailable")))
|
||
{
|
||
// We record in the registry that the engine was not initializable.
|
||
nRet = GetLastError();
|
||
dwIcsStatus = ICS_ENGINE_FAILED;
|
||
FreeLibrary ( hIcsDll );
|
||
__leave;
|
||
}
|
||
|
||
dwIcsStatus = ICS_ENGINE_NOT_COMPLETE;
|
||
if ((nRet = RegSetValueEx(hIcsRegKey, cszIcsStatusValueName, 0, REG_DWORD, (BYTE*)&dwIcsStatus, sizeof(DWORD))) != ERROR_SUCCESS)
|
||
{
|
||
nRet = GetLastError();
|
||
dwIcsStatus = ICS_ENGINE_FAILED;
|
||
__leave;
|
||
}
|
||
else
|
||
{
|
||
__try
|
||
{
|
||
if (bIsIcsAvailable = lpfndll_IsIcsAvailable()) {
|
||
// ICS is available
|
||
dwIcsStatus = ICS_IS_AVAILABLE;
|
||
nRet = ERROR_SUCCESS;
|
||
} else {
|
||
dwIcsStatus = ICS_IS_NOT_AVAILABLE;
|
||
nRet = ERROR_SUCCESS;
|
||
}
|
||
}
|
||
// exception-handlign is used to prevent IsIcsAvailable from
|
||
// killing OOBE by generating an Invalid Page Fault.
|
||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||
{
|
||
dwIcsStatus = ICS_IS_NOT_AVAILABLE;
|
||
nRet = ERROR_SUCCESS;
|
||
}
|
||
}
|
||
}
|
||
__finally
|
||
{
|
||
// perform registry update of the status.
|
||
if ((nRet = RegSetValueEx (hIcsRegKey, cszIcsStatusValueName, 0, REG_DWORD, (BYTE*)&dwIcsStatus, sizeof(DWORD))) != ERROR_SUCCESS)
|
||
{
|
||
nRet = GetLastError();
|
||
}
|
||
|
||
RegCloseKey (hIcsRegKey);
|
||
|
||
// unload library
|
||
if (hIcsDll) FreeLibrary (hIcsDll);
|
||
}
|
||
return nRet;
|
||
}
|
||
|
||
// not used. see remarks for IcsEngine() above.
|
||
DWORD CIcsMgr::CreateIcsBot()
|
||
{
|
||
LPTHREAD_START_ROUTINE lpfn_ThreadProc = (LPTHREAD_START_ROUTINE) IcsEngine;
|
||
m_hBotThread = CreateThread (NULL, NULL, lpfn_ThreadProc, 0, 0, &m_dwBotThreadId);
|
||
if (!m_hBotThread)
|
||
{
|
||
// Thread was not created
|
||
m_dwBotThreadId = 0;
|
||
m_hBotThread = 0;
|
||
return ICSMGR_ICSBOT_CREATION_FAILED;
|
||
} else
|
||
{
|
||
return ICSMGR_ICSBOT_CREATED;
|
||
}
|
||
}
|
||
|
||
// this function spawns a thread that listens for ICS connectivity changes on the Host machine.
|
||
// the function will ALSO work on the Host machine itself.
|
||
// this uses UDP sockets. See the Ics beacon protocol [bjohnson] for details.
|
||
DWORD CIcsMgr::CreateIcsDialMgr()
|
||
{
|
||
LPTHREAD_START_ROUTINE lpfn_ThreadProc = (LPTHREAD_START_ROUTINE) IcsDialStatusProc;
|
||
|
||
if ( bIsDialThreadAlive || m_hDialThread || m_dwDialThreadId)
|
||
{
|
||
return ERROR_SERVICE_ALREADY_RUNNING;
|
||
}
|
||
m_hDialThread = CreateThread (NULL, NULL, lpfn_ThreadProc, (LPVOID)(&m_pfnIcsConn), 0, &m_dwDialThreadId);
|
||
if (!m_hDialThread)
|
||
{
|
||
// Thread was not created
|
||
m_hDialThread = 0;
|
||
m_dwDialThreadId = 0;
|
||
return GetLastError();
|
||
}
|
||
else
|
||
{
|
||
return ERROR_SUCCESS;
|
||
}
|
||
}
|
||
|
||
// this now relies
|
||
BOOL CIcsMgr::IsIcsAvailable() {
|
||
return bIsBroadbandIcsAvailable;
|
||
}
|
||
|
||
|
||
BOOL CIcsMgr::IsIcsHostReachable()
|
||
{
|
||
return IsDestinationReachable ( cszIcsHostIpAddress, 0 );
|
||
}
|
||
|
||
DWORD CIcsMgr::RefreshIcsDialStatus()
|
||
{
|
||
INT n = 0;
|
||
|
||
u_short usServerPort = 2869;
|
||
struct sockaddr_in saddr;
|
||
INT saddr_len = sizeof ( saddr );
|
||
BYTE lpbRequestBuf[100];
|
||
DWORD dwRequestBufSize = sizeof ( lpbRequestBuf );
|
||
LPDWORD pdw = 0;
|
||
WCHAR *lpbie = 0;
|
||
WCHAR *lpbBound = 0;
|
||
SOCKET s = INVALID_SOCKET;
|
||
DWORD nRet = ERROR_SUCCESS;
|
||
|
||
if ( !bIsWinsockInitialized )
|
||
{
|
||
return WSANOTINITIALISED;
|
||
}
|
||
|
||
if ( (s = socket ( AF_INET, SOCK_DGRAM, 0 )) == INVALID_SOCKET )
|
||
{
|
||
return E_FAIL; // for want of a better return value *BUGBUG*
|
||
// TRACE ( L"SOCKET Error.\t:%d:\n", WSAGetLastError() );
|
||
}
|
||
else
|
||
{
|
||
__try
|
||
{
|
||
USES_CONVERSION;
|
||
memset ( &saddr, 0, sizeof (saddr) );
|
||
saddr.sin_family = AF_INET;
|
||
saddr.sin_addr.S_un.S_addr = inet_addr (W2A(cszIcsHostIpAddress));
|
||
saddr.sin_port = htons ( usServerPort );
|
||
|
||
// set up request packet:
|
||
memset ( lpbRequestBuf, 0, sizeof( lpbRequestBuf ) );
|
||
|
||
|
||
// setting up the request buffer.
|
||
pdw = (PDWORD) lpbRequestBuf;
|
||
pdw[0] = 125152 & ~(0xC0000000); // random ID
|
||
pdw[1] = 20;
|
||
pdw[2] = ICSLAP_GENERAL_STATUS & ~(0x80000000);
|
||
pdw[3] = 0;
|
||
pdw[4] = 12;
|
||
|
||
|
||
if ( (n = sendto ( s, (CHAR*)lpbRequestBuf, 20, 0, (struct sockaddr *) &saddr, saddr_len )) == SOCKET_ERROR )
|
||
{
|
||
nRet = WSAGetLastError();
|
||
__leave;
|
||
}
|
||
else
|
||
{
|
||
nRet = ERROR_SUCCESS;
|
||
__leave;
|
||
}
|
||
}
|
||
__finally
|
||
{
|
||
// graceful shutdown of the socket.
|
||
shutdown ( s, SD_BOTH );
|
||
closesocket ( s );
|
||
}
|
||
}
|
||
return nRet;
|
||
}
|
||
|