#include "private.h"
#include "offline.h"

#undef TF_THISMODULE
#define TF_THISMODULE TF_DIALMON


// prototypes
DIALPROPDATA * InitDialData(void);

//
// uuid for our command target
//
const UUID CGID_ConnCmdGrp = { 0x1dc1fd0, 0xdc49, 0x11d0, {0xaf, 0x95, 0x00, 0xc0, 0x4f, 0xd9, 0x40, 0xbe} };

//
// Registry keys we use to get autodial information
//
                                                                     
// Key name
const TCHAR c_szAutodial[]  = TEXT("EnableAutodial");
const TCHAR c_szProxy[]     = TEXT("ProxyServer");

const TCHAR c_szUnknown[]   = TEXT("<unknown>");

// from schedule.cpp
extern TCHAR szInternetSettings[];
extern TCHAR szProxyEnable[];

// length of general text strings
#define TEXT_LENGTH     200

// our connection agent instance
CConnectionAgent *pAgent = NULL;

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//
//                 Connection Agent class factory helper
//
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

HRESULT CConnectionAgent_CreateInstance(LPUNKNOWN pUnkOuter, IUnknown **ppunk)
{
    IUnknown            *punk, *punkClient = NULL;
    HRESULT             hr;
    CConnClient         *pClient;

    *ppunk = NULL;

    // create new con client instance
    pClient = new CConnClient;

    if(NULL == pClient)
        return E_OUTOFMEMORY;

    // Look for CConnectionAgent in ROT
    hr = GetActiveObject(CLSID_ConnectionAgent, NULL, &punk);
    if (NULL == punk) {
        // Not there - create one
        ASSERT(NULL == pAgent);
        pAgent = new CConnectionAgent;

        if(NULL == pAgent) {
            pClient->Release();
            return E_OUTOFMEMORY;
        }

        // Get an IUnknown on new object
        hr = pAgent->QueryInterface(IID_IUnknown, (LPVOID *)&punk);
        if (FAILED(hr) || NULL == punk) {
            SAFERELEASE(pAgent);
            pClient->Release();
            return E_FAIL;
        }

        // Register new connection agent
        hr = RegisterActiveObject(punk, CLSID_ConnectionAgent,
                    ACTIVEOBJECT_STRONG, &pAgent->m_dwRegisterHandle);
        SAFERELEASE(pAgent);
        if (FAILED(hr))
        {
            DBG_WARN("CConnectionAgentClassFactory RegisterActiveObject failed.");
            pClient->Release();
            punk->Release();
            return hr;
        }
        DBG("New connection agent object created.");
    }
#ifdef DEBUG
    else
    {
        DBG("Using existing connection agent object.");
    }
#endif

    // Now we have pClient and punk.  Tell client who the boss is
    hr = pClient->SetConnAgent(punk);
    punk->Release();
    if(FAILED(hr)) {
        pClient->Release();
        return E_FAIL;
    }

    *ppunk = (IOleCommandTarget *)pClient;

    return hr;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//
//                          Helper functions
//
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//
//                             Ping Class
//
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// helper stuff
//
#define IS_DIGIT(ch)    InRange(ch, TEXT('0'), TEXT('9'))

void UnloadICMP(void);

long        g_lPingWndReg = 0;
const TCHAR c_szPingWndClass[] = TEXT("PingClass");

#define WM_NAME     (WM_USER)
#define WM_STOP     (WM_USER+1)

class CPing                  
{
protected:
    HANDLE  _hPing;
    HANDLE  _hAsync;
    HWND    _hwnd;
    UINT    _uTimerID;
    BOOL    _fResult;
    UINT    _uTimeoutSec;

    BOOL    EnableAutodial(BOOL fEnable);

public:
    CPing();
    ~CPing();

    BOOL    Init(UINT uTimeoutSec);
    BOOL    PingSite(LPTSTR pszSite);

static LRESULT CPing::WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

};

CPing::CPing()
{
    _hPing = INVALID_HANDLE_VALUE;
    _hwnd = NULL;
    _hAsync = NULL;
    _uTimeoutSec = 10;
}

CPing::~CPing()
{
    if(_hwnd)
        DestroyWindow(_hwnd);

    if(_hPing)
        IcmpCloseHandle(_hPing);

    UnloadICMP();
}

BOOL
CPing::Init(UINT uTimeoutSec)
{
    // save timeout
    _uTimeoutSec = uTimeoutSec;

    // load ICMP.DLL and get a ping handle
    _hPing = IcmpCreateFile();
    if(INVALID_HANDLE_VALUE == _hPing)
        return FALSE;

    // register window class if necessary
    if(!g_lPingWndReg) {
        g_lPingWndReg++;
        WNDCLASS wc;

        wc.style = 0;
        wc.lpfnWndProc = CPing::WndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = g_hInst;
        wc.hIcon = NULL;
        wc.hCursor = NULL;
        wc.hbrBackground = (HBRUSH)NULL;
        wc.lpszMenuName = NULL;
        wc.lpszClassName = c_szPingWndClass;

        RegisterClass(&wc);
    }

    if(NULL == _hwnd)
        _hwnd = CreateWindow(c_szPingWndClass, NULL, WS_OVERLAPPED,
                CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                NULL, NULL, g_hInst, (LPVOID)this);

    if(NULL == _hwnd)
        return FALSE;

    return TRUE;
}

LRESULT
CPing::WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    CPing *pping = (CPing *)GetWindowLong(hWnd, GWL_USERDATA);
    LPCREATESTRUCT pcs = NULL;

    switch(Msg) {
    case WM_CREATE:
        pcs = (LPCREATESTRUCT)lParam;
        SetWindowLong(hWnd, GWL_USERDATA, ((LONG) (pcs->lpCreateParams)));
        break;
    case WM_NAME:
        // gethostbyname completed
        if(0 == WSAGETASYNCERROR(lParam))
            pping->_fResult = TRUE;

        // fall through to WM_TIMER

    case WM_TIMER:
        // we ran out of time
        PostMessage(hWnd, WM_STOP, 0, 0);
        break;
    default:
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }

    return 0;
}

BOOL
CPing::EnableAutodial(BOOL fEnable)
{
    DWORD   dwNewState = 0, dwOldState = 0, dwSize = sizeof(DWORD);
    BOOL    fOldEnable = FALSE;

    if(g_fIsWinNT) {

        //
        // In NT land, 1 means disabled, 0 means enabled
        //

        // Get WinNT autodial state
        _RasGetAutodialParam(RASADP_LoginSessionDisable, &dwOldState, &dwSize);
        if(0 == dwOldState) fOldEnable = TRUE;

        // set new state
        if(FALSE == fEnable) dwNewState = 1;
        _RasSetAutodialParam(RASADP_LoginSessionDisable, &dwNewState, sizeof(DWORD));

    } else {

        //
        // In Win95 land, 1 means enabled, 0 means disabled
        //

        // Get Win95 autodial state
        if(!ReadRegValue(HKEY_CURRENT_USER, szInternetSettings, c_szAutodial,
                    &dwOldState, sizeof(DWORD)))
            dwOldState = 0;
        if(dwOldState) fOldEnable = TRUE;

        // set new state
        if(fEnable) dwNewState = 1;
        WriteRegValue(HKEY_CURRENT_USER, szInternetSettings, c_szAutodial,
            &dwNewState, sizeof(DWORD), REG_BINARY);
    }

    return fOldEnable;
}

BOOL
CPing::PingSite(LPTSTR pszHost)
{
    IPAddr          ipAddress = INADDR_NONE;
    DWORD           dwPingSend = 0xdeadbeef, dwCount;
    WSADATA         wsaData;
    BOOL            fOldState;
    TCHAR           pszWork[1024], *pEnd;
    BYTE            pGetHostBuff[MAXGETHOSTSTRUCT];
    int             iErr;

    // assume failure
    _fResult = FALSE;

    if(INVALID_HANDLE_VALUE == _hPing) {
        DBG("CPing::PingSite no ICMP handle");
        return FALSE;
    }

    // fire up winsock
    if(iErr = WSAStartup(0x0101, &wsaData)) {
        TraceMsg(TF_THISMODULE,"CPing::PingSite WSAStartup failed, iErr=%d", iErr);
        return FALSE;
    }
 
    if(IS_DIGIT(*pszHost)) {
        // try to convert ip address
        ipAddress = inet_addr(pszHost);
    }

    // turn off autodial
    fOldState = EnableAutodial(FALSE);

    if(INADDR_NONE == ipAddress) {

        // strip port (if any) from host name
        lstrcpyn(pszWork, pszHost, ARRAYSIZE(pszWork));
        pEnd = StrChr(pszWork, TEXT(':'));
        if(pEnd)
            *pEnd = 0;

        // start async gethostbyname
        _uTimerID = SetTimer(_hwnd, 1, _uTimeoutSec * 1000, NULL);
        _hAsync = WSAAsyncGetHostByName(_hwnd, WM_NAME, pszWork,
                    (char *)pGetHostBuff, MAXGETHOSTSTRUCT);

        if(_hAsync) {
            // operation started... wait for completion or time out
            MSG msg;
            while(1) {
                GetMessage(&msg, _hwnd, 0, 0);
                if(msg.message == WM_STOP)
                    break;
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            } /* while */

            if(_fResult) {
                // it worked, snarf address
                struct hostent *phe;
                phe = (struct hostent *)pGetHostBuff;
                memcpy(&ipAddress, phe->h_addr, sizeof(IPAddr));
            } else {
                // If we timed out, clean up pending request
                WSACancelAsyncRequest(_hAsync);
            }

#ifdef DEBUG
        } else {
            // operation failed to start
            iErr = WSAGetLastError();
            TraceMsg(TF_THISMODULE, "CPing::PingSite WSAAsyncGetHostByName failed, error=%d", iErr);
#endif
        }

        // kill the timer
        if(_uTimerID) {
            KillTimer(_hwnd, _uTimerID);
            _uTimerID = 0;
        }
    }

    // assume the ping will fail
    _fResult = FALSE;

    if(INADDR_NONE != ipAddress) {
        // try to ping that address
        dwCount = IcmpSendEcho(
            _hPing,
            ipAddress,
            &dwPingSend,           
            sizeof(DWORD),
            NULL,
            pszWork,
            sizeof(pszWork),
            _uTimeoutSec * 1000);

        if(dwCount) {
            // ping succeeded!!
            _fResult = TRUE;
#ifdef DEBUG
        } else {
            // didn't work - spew
            iErr = GetLastError();
            TraceMsg(TF_THISMODULE, "CPing::PingSite IcmpSendEcho failed, error=%x", iErr);
#endif
        }
    }

    // restore autodial
    EnableAutodial(fOldState);

#ifdef DEBUG
    if(_fResult)
        TraceMsg(TF_THISMODULE, "CPing::PingSite ping <%s> success", pszHost);
    else
        TraceMsg(TF_THISMODULE, "CPing::PingSite ping <%s> FAILURE", pszHost);
#endif

    WSACleanup();
    return _fResult;
}

//
// PingProxy - exported function to decide if the proxy is available or not
//
BOOL PingProxy(UINT uTimeoutSec)
{
    BOOL    fRet = FALSE;
    TCHAR   pszProxy[TEXT_LENGTH], *pszHttp, *pszSemi;
    DWORD   dwValue;

    // check for proxy enabled
    if(ReadRegValue(HKEY_CURRENT_USER, szInternetSettings, szProxyEnable,
            &dwValue, sizeof(DWORD))) {
        if(0 == dwValue)
            return FALSE;
    }

    // proxy is enabled in registry.  Ping it to see if it's around.
    if(ReadRegValue(HKEY_CURRENT_USER, szInternetSettings, c_szProxy,
            pszProxy, TEXT_LENGTH)) {

        // if there's an '=' in the proxy string, we'll look for the http= proxy
        if(NULL != StrChr(pszProxy, '=')) {
            pszHttp = StrStrI(pszProxy, TEXT("http="));
            if(NULL == pszHttp)
                // don't understand proxy string
                return FALSE;
            pszHttp += 5;       // 5 chars in "http="

            // remove following entries - they're separated by ;
            pszSemi = StrChr(pszHttp, ';');
            if(pszSemi)
                *pszSemi = 0;
        } else {
            pszHttp = pszProxy;
        }

        // got a proxy, crack the host name out of it
        TCHAR *pszPingSite;
        URL_COMPONENTS comp;
        ZeroMemory(&comp, sizeof(comp));
        comp.dwStructSize = sizeof(comp);
        comp.dwHostNameLength = 1;

        if(InternetCrackUrlA(pszHttp, 0, 0, &comp) && (comp.nScheme != INTERNET_SCHEME_UNKNOWN)) {
            pszPingSite = comp.lpszHostName;
            pszPingSite[comp.dwHostNameLength] = 0;
        } else {
            pszPingSite = pszHttp;
        }

        // ping it
        CPing ping;
        if(ping.Init(uTimeoutSec))
            fRet = ping.PingSite(pszPingSite);
    }

    return fRet;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//
//                      Connection Client object
//
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// Constructor / Destructor
//
CConnClient::CConnClient()
{
    m_cRef = 1;
    m_poctAgent = NULL;
    m_pReport = NULL;
    m_State = CLIENT_NEW;
    m_bstrURL = NULL;
}

//////////////////////////////////////////////////////////////////////////

//
// IUnknown members
//
STDMETHODIMP CConnClient::QueryInterface(REFIID riid, void ** ppv)
{
    *ppv=NULL;

    // Validate requested interface
    if ((IID_IUnknown == riid) ||
        (IID_INotificationSink == riid))
    {
        *ppv=(INotificationSink*)this;
    } else if(IID_IOleCommandTarget == riid) {
        *ppv=(IOleCommandTarget*)this;
    } else {
        return E_NOINTERFACE;
    }

    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG) CConnClient::AddRef(void)
{
    TraceMsg(TF_THISMODULE, "CConnClient::Addref (%08x) m_cRef=%d", this, m_cRef+1);
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CConnClient::Release(void)
{
    TraceMsg(TF_THISMODULE, "CConnClient::Release (%08x) m_cRef=%d", this, m_cRef-1);
    if( 0L != --m_cRef )
        return m_cRef;

    DBG("CConClient::Release Bye Bye");

    // Make sure we're disconnected
    Disconnect();

    m_poctAgent->Release();

    if(m_bstrURL)
        SysFreeString(m_bstrURL);

    delete this;
    return 0L;
}

//////////////////////////////////////////////////////////////////////////

//
// CConClient helper functions
//
HRESULT CConnClient::SetConnAgent(IUnknown *punk)
{
    return punk->QueryInterface(IID_IOleCommandTarget, (void **)&m_poctAgent);
}

HRESULT CConnClient::Connect()
{
    HRESULT     hr;
    VARIANTARG  vin, vout;

    m_State = CLIENT_CONNECTING;

    // tell agent we want to connect
    vin.vt = VT_UNKNOWN;
    vin.punkVal = (IOleCommandTarget *)this;
    VariantInit(&vout);
    hr = m_poctAgent->Exec(&CGID_ConnCmdGrp, AGENT_CONNECT, 0, &vin, &vout);
    if(SUCCEEDED(hr)) {
        ASSERT(vout.vt == VT_I4);
        m_iCookie = vout.lVal;
    }

    return hr;
}

HRESULT CConnClient::Disconnect()
{
    HRESULT     hr;
    VARIANTARG  vin;

    if(CLIENT_DISCONNECTED == m_State)
        return S_OK;

    m_State = CLIENT_DISCONNECTED;

    // tell agent we want to disconnect
    vin.vt = VT_I4;
    vin.ulVal = m_iCookie;
    hr = m_poctAgent->Exec(&CGID_ConnCmdGrp, AGENT_DISCONNECT, 0, &vin, NULL);

    // done with report pointer
    SAFERELEASE(m_pReport);

    return hr;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// INotificationSink members
//
STDMETHODIMP CConnClient::OnNotification(
        LPNOTIFICATION         pNotification,
        LPNOTIFICATIONREPORT   pNotificationReport,
        DWORD                  dwReserved
        )
{
    NOTIFICATIONTYPE    nt;
    HRESULT             hr = S_OK;

    hr = pNotification->GetNotificationInfo(&nt, NULL,NULL,NULL,0);

    if(FAILED(hr)) {
        DBG_WARN("CConnClient::OnNotification failed to get not type!");
        return E_INVALIDARG;
    }

    if(IsEqualGUID(nt, NOTIFICATIONTYPE_AGENT_START)) {

        DBG("CConnClient::OnNotification AGENT_START");

        if(CLIENT_NEW == m_State) {

            // Must have a report pointer!
            if(NULL == pNotificationReport) {
                DBG("CConnClient::OnNotification no report on START!");
                return E_UNEXPECTED;
            }

            // save report pointer
            TraceMsg(TF_THISMODULE, "CConClient::OnNotification (%08x) addreffing report pointer", this);
            m_pReport = pNotificationReport;
            m_pReport->AddRef();

            // get the URL
            hr = ReadBSTR(pNotification, NULL, c_szPropURL, &m_bstrURL);

            // convert to ansi and log url connection request
            TCHAR pszURL[INTERNET_MAX_URL_LENGTH];

            MyOleStrToStrN(pszURL, INTERNET_MAX_URL_LENGTH, m_bstrURL);
            LogEvent("Connecting for <%s>", pszURL);

            // Tell agent to connect
            Connect();

        } else {
            DBG("CConnClient::OnNotification unexpected connect");
            return E_UNEXPECTED;
        }

    } else if(IsEqualGUID(nt, NOTIFICATIONTYPE_TASKS_COMPLETED)) {
        DBG("CConnClient::OnNotification TASKS_COMPLETED");

        // convert url to ansi
        TCHAR pszURL[INTERNET_MAX_URL_LENGTH];
        MyOleStrToStrN(pszURL, INTERNET_MAX_URL_LENGTH, m_bstrURL);

        switch(m_State) {
        case CLIENT_CONNECTING:
            m_State = CLIENT_ABORT;

            // log connection abort
            LogEvent("Aborting connection for <%s>", pszURL);

            break;
        case CLIENT_CONNECTED:
            // log disconnect
            LogEvent("Disconnecting for <%s>", pszURL);

            Disconnect();
            break;
        default:
            DBG("CConnClient::OnNotification unexpected disconnect");
            return E_UNEXPECTED;
        }
    } else {
        DBG("CConnClient::OnNotification unknown type");
    }

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// IOleCommandTarget members
//
STDMETHODIMP CConnClient::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds,
                                    OLECMD prgCmds[], OLECMDTEXT *pCmdText)
{
    if (IsEqualGUID(*pguidCmdGroup, CGID_ConnCmdGrp)) {
        return S_OK;
    }

    return OLECMDERR_E_UNKNOWNGROUP;
}

STDMETHODIMP CConnClient::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
                             DWORD nCmdexecopt, VARIANTARG *pvaIn,
                             VARIANTARG *pvaOut)
{
    HRESULT hr = E_NOTIMPL;

    if (pguidCmdGroup &&  IsEqualGUID(*pguidCmdGroup, CGID_ConnCmdGrp))
    {
        switch(nCmdID) {
        case AGENT_NOTIFY:
            if(VT_ERROR == pvaIn->vt) {
                hr = DeliverProgressReport(pvaIn->scode, NULL);
            } else {
                hr = E_INVALIDARG;
            }
            break;
        }
    }
    
    return hr;
}

//////////////////////////////////////////////////////////////////////////

//
// Other methods
//
HRESULT CConnClient::DeliverProgressReport(SCODE scode, BSTR bstrErrorText)
{
    HRESULT             hr = S_OK;
    INotificationMgr    *pMgr;
    INotification       *pStatus;

    switch(m_State) {
    case CLIENT_CONNECTING:
        // Get Notification manager
        hr = CoCreateInstance(CLSID_StdNotificationMgr, NULL,
                CLSCTX_INPROC_SERVER, IID_INotificationMgr, (void**)&pMgr);
        if(FAILED(hr))
            return hr;

        // create notification to deliver
        hr = pMgr->CreateNotification(NOTIFICATIONTYPE_PROGRESS_REPORT,
            (NOTIFICATIONFLAGS)0, NULL, &pStatus, 0);
        pMgr->Release();
        if(FAILED(hr))
            return hr;

        // stick result and string in progress report
//      WriteOLESTR(pStatus, NULL, c_szPropStatusString, bstrErrorText);
        WriteSCODE(pStatus, NULL, c_szPropStatusCode, scode);

        // deliver notification
        hr = m_pReport->DeliverUpdate(pStatus, 0, 0);
        pStatus->Release();

        if(SUCCEEDED(scode)) {
            m_State = CLIENT_CONNECTED;
        } else {
            Disconnect();
        }
        break;
    case CLIENT_ABORT:
        Disconnect();
        break;
    }

    return hr;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//
//                          Connection agent
//
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// Constructor and destructor
//
CConnectionAgent::CConnectionAgent()
{
    m_pData = NULL;
    m_cRef = 1;
    m_dwRegisterHandle = 0;

    m_lConnectionCount = 0;
    m_dwFlags = 0;
    m_hdpaClient = NULL;

    // Get the notification manager
    m_pMgr = NULL;
    CoCreateInstance(CLSID_StdNotificationMgr, NULL,
                CLSCTX_INPROC_SERVER, IID_INotificationMgr, (void**)&m_pMgr);
}

CConnectionAgent::~CConnectionAgent()
{
    SAFERELEASE(m_pMgr);
    SAFELOCALFREE(m_pData);

    if(IsFlagSet(m_dwFlags, CA_LOADED_RAS))
        UnloadRasDLL();
}

//
// Clean - clean up strong lock and ref count
//
void
CConnectionAgent::Clean(void)
{
    DWORD   dwHandle = m_dwRegisterHandle;
    int     iCurReport, i;
    CLIENTINFO *pClient;

    // don't do anything if there are outstanding connections
    if(m_lConnectionCount)
        return;

    // clean up client dpa
    if(m_hdpaClient) {
        iCurReport = DPA_GetPtrCount(m_hdpaClient);
        for(i=0; i<iCurReport; i++) {
            pClient = (CLIENTINFO *)(DPA_GetPtr(m_hdpaClient, i));
            if(pClient)
                delete pClient;
        }
        DPA_Destroy(m_hdpaClient);
        m_hdpaClient = NULL;
    }

    // release our strong registration
    DBG("CConnectionAgent::Clean revoking connection agent object");
    m_dwRegisterHandle = 0;
    RevokeActiveObject(dwHandle, NULL);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// Connect - entry point to connect
//
void
CConnectionAgent::Connect(void)
{
    BOOL            fDone = FALSE;
    TCHAR           pszText[TEXT_LENGTH];

    //
    // increment connection count
    //
    m_lConnectionCount ++;
    TraceMsg(TF_THISMODULE, "CConnectionAgent::Connect ref count now %d", m_lConnectionCount);

    //
    // Store offline state if we haven't already and go online
    //
    if(FALSE == IsFlagSet(m_dwFlags, CA_OFFLINE_STATE_READ)) {

        if(IsGlobalOffline())
            SetFlag(m_dwFlags, CA_OFFLINE);

        // make sure we're online
        SetGlobalOffline(FALSE);

        SetFlag(m_dwFlags, CA_OFFLINE_STATE_READ);
    }

    //
    // check for pending dialup connection
    //
    if(IsFlagSet(m_dwFlags, CA_CONNECTING_NOW)) {
        // already working on a connection
        DBG("CConnectionAgent::Connect already trying to connect");
        return;
    }

    //
    // check to see if we can dial to get a connection
    //
    if(FALSE == IsDialPossible()) {

        //
        // can't dial - better have a direct connection
        //
        DBG("CConnectionAgent::Connect guessing connected");
        if(!MLLoadString(IDS_DIAL_DIRECT, pszText, TEXT_LENGTH))
            lstrcpy(pszText, "direct");
        Notify(S_OK, pszText);
        return;
    }

    //
    // check for an existing dialup connection
    //

    if(FALSE == IsFlagSet(m_dwFlags, CA_LOADED_RAS)) {
        SetFlag(m_dwFlags, CA_LOADED_RAS);
        LoadRasDLL();
    }

    if(IsDialExisting()) {
        DBG("CConnectionAgent::Connect already connected");
        if(!MLLoadString(IDS_DIAL_ALREADY_CONNECTED, pszText, TEXT_LENGTH))
            lstrcpy(pszText, "success");
        Notify(S_OK, pszText);
        return;
    }

#ifdef COOL_MODEM_ACTION
    // since we assume connected for direct connect, no proxy check is
    // necessary.  If this check is put in, cool behavior results: It uses
    // the proxy server if it can find it, otherwise it autodials.

    // Autodial needs to be made consistant with this before it gets turned
    // on

    //
    // check for a proxy connection
    //
    if(PingProxy(5)) {
        // Dial is possible but proxy is available! Turn off autodial and
        // flag to turn it back on when we're done
        CPing::EnableAutodial(FALSE);
        SetFlag(m_dwFlags, CA_AUTODIAL_OFF);

        // Using proxy
        DBG("CConnectionAgent::Connect using proxy");
        if(!MLLoadString(IDS_DIAL_PROXY, pszText, TEXT_LENGTH))
            lstrcpy(pszText, "proxy");
        Notify(S_OK, pszText);
        return;
    }
#endif

    /////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////

    //
    // Ensure we can dial without user intervention
    //
    BOOL fContinue = TRUE;

    if(SHRestricted2W(REST_NoUnattendedDialing, NULL, 0))
        // dialing restricted
        fContinue = FALSE;

    //
    // No existing connection but we can dial.  do it now.
    //
    SetFlag(m_dwFlags, CA_CONNECTING_NOW);

    
    DWORD dwRetCode;
    if(fContinue)
    {
        dwRetCode = ERROR_SUCCESS;
        if(!InternetAutodial((INTERNET_AUTODIAL_FORCE_ONLINE | 
                                        INTERNET_AUTODIAL_FORCE_UNATTENDED | 
                                        INTERNET_AUTODIAL_FAILIFSECURITYCHECK), 0))
        {
            dwRetCode = GetLastError();
        }
                
    }
                                        
    if (fContinue && (ERROR_SUCCESS == dwRetCode)) {
        // successful connection made
        SetFlag(m_dwFlags, CA_DIALED);
        if(!MLLoadString(IDS_DIAL_SUCCESS, pszText, TEXT_LENGTH))
            lstrcpy(pszText, "success");
        Notify(S_OK, pszText);
    } else {
        //UINT uID;
        HRESULT hrDialResult;
        // unable to dial
        if(ERROR_INTERNET_FAILED_DUETOSECURITYCHECK == dwRetCode)
        {
            hrDialResult = E_ABORT;
            // uID = IDS_STRING_E_SECURITYCHECK; not needed - since MLLoadString below is commented out
        }
        else
        {
            hrDialResult = E_INVALIDARG;
            // uID = IDS_STRING_E_CONFIG;not needed - since MLLoadString below is commented out
        }
        
     // if(!MLLoadString(uID, pszText, TEXT_LENGTH)) // Don't bother : This is ignored anyway
        lstrcpy(pszText, "Connection Not Made");
        Notify(hrDialResult, pszText);
    }

    ClearFlag(m_dwFlags, CA_CONNECTING_NOW);
}

//
// Disconnect - entry point to disconnect
//
void
CConnectionAgent::Disconnect(void)
{
    // If we're the last connection, hang up
    m_lConnectionCount --;
    TraceMsg(TF_THISMODULE, "CConnectionAgent::Disconnect ref count now %d", m_lConnectionCount);

    if(0 == m_lConnectionCount) {

        // If we dialed this connection, hang it up
        if(IsFlagSet(m_dwFlags, CA_DIALED)) {
            LogEvent(TEXT("EVT: Hanging up"));
            InternetAutodialHangup(0);
        }

#ifdef COOL_MODEM_ACTION
        if(IsFlagSet(m_dwFlags, CA_AUTODIAL_OFF)) {
            // we turned autodial off - turn it back on
            CPing::EnableAutodial(TRUE);

            // tell wininet we've changed it
            InternetSetOption(NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0);
        }
#endif

        // restore original offline state
        SetGlobalOffline(IsFlagSet(m_dwFlags, CA_OFFLINE));

        // revoke our object
        Clean();
    }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// IsDialPossible - search around and make sure all necessary info
// is available to connect.  If fQuiet is false, may bring up dialogs
// to get necessary info
//
BOOL
CConnectionAgent::IsDialPossible()
{
    BOOL    fPossible = TRUE;

    // Refresh data in case properties has been up and changed it
    if(m_pData) 
        MemFree(m_pData);
    m_pData = InitDialData();

    if(NULL == m_pData)
        return FALSE;

    if(FALSE == m_pData->fEnabled)
        // not enabled
        fPossible = FALSE;
    if(!m_pData->pszConnection[0])
        // no connection
        fPossible = FALSE;

    return fPossible;
}


//
// IsDialExisting - check to see if there's an existing dialup connection
//                  that we didn't do
//

#define MAX_CONNECTION 8

BOOL
CConnectionAgent::IsDialExisting(void)
{
    TCHAR   pszConn[RAS_MaxEntryName+1];
    RASCONN pRasCon[MAX_CONNECTION]; 
    DWORD   dwSize = MAX_CONNECTION * sizeof(RASCONN), dwConn, dwCur;
    HKEY    hkeyRoot = HKEY_CURRENT_USER;

    // read internet connectoid from registry
    if(!ReadRegValue(hkeyRoot, c_szRASKey, c_szProfile, pszConn,
            RAS_MaxEntryName+1)) {
        DBG("CConnectionAgent::IsDialExisting unable to read internet connectoid");
        return FALSE;
    }

    // have Ras enumerate existing connections 
    pRasCon[0].dwSize = sizeof(RASCONN);
    if(_RasEnumConnections(pRasCon, &dwSize, &dwConn)) {
        DBG("CConnectionAgent::IsDialExisting RasEnumConnections failed");
        return FALSE;
    }

    // do any of them match our internet connectoid?
    for(dwCur=0; dwCur<dwConn; dwCur++) {
        if(0 == lstrcmp(pszConn, pRasCon[dwCur].szEntryName))
            return TRUE;
    }

    return FALSE;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// IUnknown members
//
STDMETHODIMP CConnectionAgent::QueryInterface(REFIID riid, void ** ppv)
{
    *ppv=NULL;

    // Validate requested interface
    if ((IID_IUnknown == riid) ||
        (IID_IOleCommandTarget == riid))
    {
        *ppv=(IOleCommandTarget*)this;
    } else {
        return E_NOINTERFACE;
    }

    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
}


STDMETHODIMP_(ULONG) CConnectionAgent::AddRef(void)
{
    TraceMsg(TF_THISMODULE, "CConnectionAgent::Addref m_cRef=%d", m_cRef+1);
    return ++m_cRef;
}


STDMETHODIMP_(ULONG) CConnectionAgent::Release(void)
{
    TraceMsg(TF_THISMODULE, "CConnectionAgent::Release m_cRef=%d", m_cRef-1);
    if( 0L != --m_cRef )
        return m_cRef;

    DBG("CConnectionAgent::Release Bye Bye");

    delete this;
    return 0L;
}


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// IOleCommandTarget members
//
STDMETHODIMP CConnectionAgent::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds,
                                    OLECMD prgCmds[], OLECMDTEXT *pCmdText)
{
    if (IsEqualGUID(*pguidCmdGroup, CGID_ConnCmdGrp))
    {
        // We like connection agent commands
        return S_OK;
    }

    return OLECMDERR_E_UNKNOWNGROUP;
}

STDMETHODIMP CConnectionAgent::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
                             DWORD nCmdexecopt, VARIANTARG *pvaIn,
                             VARIANTARG *pvaOut)
{
    HRESULT     hr;
    CLIENTINFO  *pInfo;
    int         iIndex;

    if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CGID_ConnCmdGrp))
    {
        switch(nCmdID) {
        case AGENT_CONNECT:
            // validate input arguments
            if(VT_UNKNOWN != pvaIn->vt || NULL == pvaOut)
                return E_INVALIDARG;

            // create dpa if necessary
            if(NULL == m_hdpaClient)
                m_hdpaClient = DPA_Create(0);
            if(NULL == m_hdpaClient)
                return E_OUTOFMEMORY;

            // create and initialize new clientinfo struct
            pInfo = new CLIENTINFO;
            if(NULL == pInfo)
                return E_OUTOFMEMORY;
            pInfo->dwFlags = 0;
            hr = pvaIn->punkVal->QueryInterface(IID_IOleCommandTarget, (void **)&pInfo->poctClient);
            if(FAILED(hr))
                return hr;

            // insert struct into dpa and return index
            iIndex = DPA_InsertPtr(m_hdpaClient, DPA_APPEND, pInfo);
            if(iIndex < 0) {
                delete pInfo;
                return E_OUTOFMEMORY;
            } else {
                pvaOut->vt = VT_I4;
                pvaOut->ulVal = iIndex;
            }

            // connect
            Connect();
            return S_OK;

        case AGENT_DISCONNECT:
            // validate input parameters
            if(VT_I4 != pvaIn->vt)
                return E_INVALIDARG;

            // mark client record as disconnected
            pInfo = (CLIENTINFO *)DPA_GetPtr(m_hdpaClient, pvaIn->lVal);
            if(pInfo) {
                pInfo->dwFlags |= CLIENT_DISCONNECT;
                SAFERELEASE(pInfo->poctClient);
            }

            // disconnect
            Disconnect();
            return S_OK;
        }
    }
    
    return E_NOTIMPL;
}


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

// 
// Notify all waiting agents of success or failure of dial attempt
//
void CConnectionAgent::Notify(HRESULT hrDialResult, TCHAR *pszErrorText)
{
    CLIENTINFO          *pClient;
    int                 i, iCurReport;
    //WCHAR               pwszStatus[TEXT_LENGTH];
    VARIANTARG          vin;

    // We're done connecting
    ClearFlag(m_dwFlags, CA_CONNECTING_NOW);

    // Create the notifications to send
    if(S_OK == hrDialResult) {
        DBG("CConnectionAgent::Notify sending ONLINE");
        LogEvent(TEXT("EVT: Successful connection"));
    } else {
        DBG("CConnectionAgent::Notify sending OFFLINE");
        LogEvent(TEXT("EVT: Unsuccessful connection - hr=%08x"), hrDialResult);
    }

    // convert string to bstr
    // MyStrToOleStrN(pwszStatus, TEXT_LENGTH, pszErrorText);

    // build exec paramaters
    vin.vt = VT_ERROR;
    vin.scode = hrDialResult;

    // Send it to all the clients
    iCurReport = DPA_GetPtrCount(m_hdpaClient);
    for(i=0; i<iCurReport; i++) {
        pClient = (CLIENTINFO *)(DPA_GetPtr(m_hdpaClient, i));
        if(pClient && 0 == pClient->dwFlags) {
            pClient->poctClient->Exec(&CGID_ConnCmdGrp, AGENT_NOTIFY, 0, 
                &vin, NULL);
            //  This can get blown away out from under us.
            if (m_hdpaClient)
            {
                pClient->dwFlags |= CLIENT_NOTIFIED;
                SAFERELEASE(pClient->poctClient);
            }
        }
    }

    // if we're disconnected, clean ourselves up
    Clean();
}

BOOL
GetLogonInfo(DIALPROPDATA *pData)
{
    RASDIALPARAMS   dp;
    DWORD           dwRes;
    BOOL            fPassword = FALSE;

    // initially set name/password/domain to null
    pData->pszUsername[0] = 0;
    pData->pszPassword[0] = 0;
    pData->pszDomain[0] = 0;

    // if there's no connection, we're done
    if(0 == pData->pszConnection[0])
        return FALSE;

    // Try and get name/password/domain from Ras
    memset(&dp, 0, sizeof(RASDIALPARAMS));
    dp.dwSize = sizeof(RASDIALPARAMS);
    lstrcpyn(dp.szEntryName, pData->pszConnection, ARRAYSIZE(dp.szEntryName));
    dwRes = _RasGetEntryDialParams(NULL, &dp, &fPassword);
    if(fPassword && 0 == dwRes) {
        // Copy ras information to pData.
        lstrcpyn(pData->pszUsername, dp.szUserName, ARRAYSIZE(pData->pszUsername));
        lstrcpyn(pData->pszPassword, dp.szPassword, ARRAYSIZE(pData->pszPassword));
        lstrcpyn(pData->pszDomain, dp.szDomain, ARRAYSIZE(pData->pszDomain));
    }

    return fPassword;
}

DIALPROPDATA * InitDialData(void)
{
    DIALPROPDATA *  pData = (DIALPROPDATA *)MemAlloc(LPTR, sizeof(DIALPROPDATA));
    HKEY            hkeyRoot = HKEY_CURRENT_USER;
    BOOL            fGotInfo = FALSE;
    DWORD           dwValue;

    if(NULL == pData)
        return NULL;

    // Fix fEnabled from registry   HKCU\...\Internet Settings\EnableAutodial
    ReadRegValue(hkeyRoot, szInternetSettings, c_szAutodial, &dwValue, sizeof(DWORD));
    if(dwValue == 1) {
        pData->fEnabled = TRUE;
    }

    // Fix fUnattended from registry   HKCU\...\Internet Settings\EnableUnattended
    ReadRegValue(hkeyRoot, szInternetSettings, c_szEnable, &dwValue, sizeof(DWORD));
    if(dwValue == 1) {
        pData->fUnattended = TRUE;
    }

    // Try to find a connection     HKCU\Remote Access\Internet Profile
    if(ReadRegValue(hkeyRoot, c_szRASKey, c_szProfile, pData->pszConnection,
        RAS_MaxEntryName+1)) {
        GetLogonInfo(pData);
    }
            
    return pData;
}