//+----------------------------------------------------------------------------
//
// File:    cmroute.cpp
//
// Module:  CMROUTE.DLL
//
// Synopsis: Route Plumbing implementation for CM, as a post-connect action
//
// Copyright (c) 1998-2000 Microsoft Corporation
//
// Author:  12-Mar-2000 SumitC  Created
//
// Note:
//
//-----------------------------------------------------------------------------

#include "pch.h"
#include "iphlpapi.h"
#include "cmdebug.h"

//
//  Function declarations  
//


HRESULT ParseArgs(LPSTR pszArgList,
                  BOOL * pfUseFile,
                  LPSTR pszRoutesFile,
                  BOOL * pfUseURL,
                  LPSTR pszRoutesURL,
                  BOOL * pfProfile,
                  LPSTR pszProfile,
                  BOOL * pfURLNotFoundIgnorable,
                  BOOL * pfKeepTempFiles);
#if 0
// see note below
HRESULT CheckIPForwarding();
#endif
HRESULT Initialize(PMIB_IPFORWARDTABLE * pRouteTable, PMIB_IPFORWARDROW * pGateway);
HRESULT GetRoutesFromFile(LPSTR pszFileName,
                          LPSTR pszProfile,
                          LPSTR * ppszRouteInfo,
                          DWORD * pcbRouteInfo);
HRESULT GetRoutesFromURL(LPSTR pszURL,
                         BOOL fKeepTempFiles,
                         LPSTR * ppszRouteInfo,
                         DWORD * pcbRouteInfo);
HRESULT ProcessRouteInfo(const LPSTR pszNewRouteInfo,
                         DWORD cbNewRouteInfo,
                         PMIB_IPFORWARDTABLE pmibRouteTable,
                         PMIB_IPFORWARDROW pGateway,
                         BOOL * pfDeleteDefaultGateway);
HRESULT DeleteDefaultGateway(PMIB_IPFORWARDROW pGateway);

//
//  Route Table functions
//
HRESULT GetRouteTable(PMIB_IPFORWARDTABLE * pTable);
DWORD GetIf(const MIB_IPFORWARDROW& route, const MIB_IPFORWARDTABLE& RouteTable);
PMIB_IPFORWARDROW GetDefaultGateway(PMIB_IPFORWARDTABLE pRouteTable);

//
//  Helper functions
//
BOOL ConvertSzToIP(LPSTR sz, DWORD& dwIP);
LPTSTR IPtoTsz(DWORD dw);
LPSTR IPtosz(DWORD dwIP, char *psz);
LPSTR StrCpyWithoutQuotes(LPSTR pszDest, LPCSTR pszSrc);
BOOL VerifyProfileAndGetServiceDir(LPSTR pszProfile);

//
//  IP Helper function prototypes
//
typedef DWORD (WINAPI *pfnCreateIpForwardEntrySpec)(PMIB_IPFORWARDROW);
typedef DWORD (WINAPI *pfnDeleteIpForwardEntrySpec)(PMIB_IPFORWARDROW);
typedef DWORD (WINAPI *pfnGetIpForwardTableSpec)(PMIB_IPFORWARDTABLE, PULONG, BOOL);

pfnCreateIpForwardEntrySpec g_pfnCreateIpForwardEntry = NULL;
pfnDeleteIpForwardEntrySpec g_pfnDeleteIpForwardEntry = NULL;
pfnGetIpForwardTableSpec g_pfnGetIpForwardTable = NULL;
HMODULE g_hIpHlpApi = NULL;

#if DBG
void PrintRouteTable();
#endif

//+----------------------------------------------------------------------------
//
// Func:    FreeIpHlpApis
//
// Desc:    This function frees the instance of iphlpapi.dll loaded through
//          LoadIpHelpApis.  Note that it also sets the module handle and all
//          of the function pointers loaded from this module to NULL.
//
// Args:    None
//
// Return:  Nothing
//
// Notes:   
//
// History: 14-Dec-2000   quintinb      Created
//
//-----------------------------------------------------------------------------
void FreeIpHlpApis(void)
{
    if (g_hIpHlpApi)
    {
        FreeLibrary(g_hIpHlpApi);
        g_hIpHlpApi = NULL;
        g_pfnCreateIpForwardEntry = NULL;
        g_pfnDeleteIpForwardEntry = NULL;
        g_pfnGetIpForwardTable = NULL;
    }
}

//+----------------------------------------------------------------------------
//
// Func:    LoadIpHelpApis
//
// Desc:    This functions loads a copy of the iphlpapi.dll and then retrieves
//          function pointers for CreateIpForwardEntry, DeleteIpForwardEntry,
//          and GetIpForwardTable.  The module handle and the function pointers
//          are stored in globals vars.
//
// Args:    None
//
// Return:  HRESULT - S_OK on success, S_FALSE on failure.  This prevents cmroute
//                    from returning an error value (which would stop the connection)
//                    but allows cmroute to exit cleanly.
//
// Notes:   
//
// History: 14-Dec-2000   quintinb      Created
//
//-----------------------------------------------------------------------------
HRESULT LoadIpHelpApis(void)
{
    HRESULT hr = S_FALSE; // we want the connection to continue but cmroute to not do anything...
    g_hIpHlpApi = LoadLibrary(TEXT("IPHLPAPI.DLL"));

    if (g_hIpHlpApi)
    {
        g_pfnCreateIpForwardEntry = (pfnCreateIpForwardEntrySpec)GetProcAddress(g_hIpHlpApi, "CreateIpForwardEntry");
        g_pfnDeleteIpForwardEntry = (pfnDeleteIpForwardEntrySpec)GetProcAddress(g_hIpHlpApi, "DeleteIpForwardEntry");
        g_pfnGetIpForwardTable = (pfnGetIpForwardTableSpec)GetProcAddress(g_hIpHlpApi, "GetIpForwardTable");

        if (g_pfnCreateIpForwardEntry && g_pfnDeleteIpForwardEntry && g_pfnGetIpForwardTable)
        {
            hr = S_OK;
        }
        else
        {
            FreeIpHlpApis();
        }
    }

    CMTRACEHR("LoadIpHelpApis", hr);
    return hr;
}

//+----------------------------------------------------------------------------
//
// Func:    SetRoutes
//
// Desc:    The entry point for handling route munging for VPN connections.
//          This is a Connection Manager connect action and uses the CM connect
//          action format (see CMAK docs for more info).  Thus the parameters
//          to the dll are passed via a string which contains parameters (see the
//          cmproxy spec for a list of the parameter values).
//
// Args:    [hWnd]    - window handle of caller
//          [hInst]   - instance handle of caller
//          [pszArgs] - argument string for connect action
//          [nShow]   - unused
//
// Return:  HRESULT
//
// Notes:   
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------

HRESULT WINAPI SetRoutes(HWND hWnd, HINSTANCE hInst, LPSTR pszArgs, int nShow)
{
    HRESULT             hr = S_OK;
    PMIB_IPFORWARDTABLE pRouteTable        = NULL;
    PMIB_IPFORWARDROW   pGateway           = NULL;
    LPSTR               pszRoutesFromFile  = NULL;
    DWORD               cbRoutesFromFile   = 0;
    LPSTR               pszRoutesFromURL   = NULL;
    DWORD               cbRoutesFromURL    = 0;
    // results of parsing the commandline
    BOOL                fUseFile = FALSE;
    BOOL                fUseURL = FALSE;
    BOOL                fProfile = FALSE;
    BOOL                fURLNotFoundIsNotFatal = FALSE;
    BOOL                fDeleteDefaultGatewayViaFile = FALSE;
    BOOL                fDeleteDefaultGatewayViaURL = FALSE;
    BOOL                fKeepTempFiles = FALSE;
    char                szRoutesFile[MAX_PATH + 1];
    char                szRoutesURL[MAX_PATH + 1];
    char                szProfile[MAX_PATH + 1];

#if 0
/*
    // start security check to block unauthorized users
    // REVIEW: remove before shipping!

    //
    // Quick and dirty security test. See if we can open hard-coded file first.
    // If file is not available, then bail out completely.
    // 
    
    lstrcpy(szRoutesFile, "\\\\sherpa\\route-plumb\\msroutes.txt");

    HANDLE hFile = CreateFile(szRoutesFile,
                              GENERIC_READ,
                              FILE_SHARE_READ,
                              NULL,
                              OPEN_EXISTING,
                              FILE_ATTRIBUTE_NORMAL,
                              NULL);

    if (INVALID_HANDLE_VALUE == hFile)
    {
        CMTRACE1("Unable to access file %s\n", szRoutesFile);
        MessageBox(NULL, "You are not authorized to use this tool.",
                   "CMROUTE.DLL Custom Action", MB_OK);
        CloseHandle(hFile);
        return E_ACCESSDENIED;
    }
    
    CloseHandle(hFile);

    // end security check
*/
#endif

    //
    //  See if we can get the function pointers we need in IP helper?
    //

    hr = LoadIpHelpApis();

    if (S_OK != hr)
    {
        goto Cleanup;
    }

    //
    //  parse args
    //
    hr = ParseArgs(pszArgs,
                   &fUseFile,
                   szRoutesFile,
                   &fUseURL,
                   szRoutesURL,
                   &fProfile,
                   szProfile,
                   &fURLNotFoundIsNotFatal,
                   &fKeepTempFiles);
    if (S_OK != hr)
    {
        goto Cleanup;
    }

#if 0
// see note below
    hr = CheckIPForwarding();
    if (S_FALSE == hr)
    {
        CMTRACE("SetRoutes: IP forwarding is enabled - cmroute won't do anything");
        hr = S_OK;
        goto Cleanup;
    }
    if (S_OK != hr)
    {
        goto Cleanup;
    }
#endif

#if DBG
    PrintRouteTable();
#endif

    //
    //  Get the routetable and default gateway
    //
    hr = Initialize(&pRouteTable, &pGateway);
    if (S_OK != hr)
    {
        goto Cleanup;
    }

    //
    //  Get the routes out of the file if asked
    //
    if (fUseFile)
    {
        hr = GetRoutesFromFile(szRoutesFile,
                               (fProfile ? szProfile : NULL),
                               &pszRoutesFromFile,
                               &cbRoutesFromFile);
        if (S_OK != hr)
        {
            goto Cleanup;
        }

#if DBG
        OutputDebugString(pszRoutesFromFile);
#endif
    }

    //
    //  Get the routes out of the URL if asked
    //
    if (fUseURL)
    {
        hr = GetRoutesFromURL(szRoutesURL,
                              fKeepTempFiles,
                              &pszRoutesFromURL,
                              &cbRoutesFromURL);
        if (S_OK != hr)
        {
            //
            //  It might have been worth adding a clause below to restrict this
            //  to "failures to access the URL", but this list of errorcodes is
            //  likely to be large (and if the system is really hosed, we'll find
            //  out soon enough).  So, bypass *all* errors if /DONT_REQUIRE_URL
            //  is set.
            //
            if (fURLNotFoundIsNotFatal)
            {
                //
                //  If URL_Access_Failure_Not_Fatal is set, don't return an error.
                //  However, we unset the flag so that we stop processing the URL.
                //
                CMTRACE("SetRoutes: dont_require_url is set, bypassing error");
                fUseURL = FALSE;
                hr = S_OK;
            }
            else
            {
                goto Cleanup;
            }
        }
        
#if DBG
        OutputDebugString(pszRoutesFromURL);
#endif
    }

    //
    //  Now set the routes
    //
    MYDBGASSERT(S_OK == hr);
    if (fUseFile)
    {
        hr = ProcessRouteInfo(pszRoutesFromFile, cbRoutesFromFile, pRouteTable, pGateway, &fDeleteDefaultGatewayViaFile);
        if (S_OK != hr)
        {
            CMTRACE1("SetRoutes: adding routes from FILE failed with %x", hr);
            goto Cleanup;
        }
    }

    MYDBGASSERT(S_OK == hr);
    if (fUseURL)
    {
        hr = ProcessRouteInfo(pszRoutesFromURL, cbRoutesFromURL, pRouteTable, pGateway, &fDeleteDefaultGatewayViaURL);
        if (S_OK != hr)
        {
            if ((E_UNEXPECTED == hr) && fURLNotFoundIsNotFatal)
            {
                // we use E_UNEXPECTED to indicate that the URL points to a .htm file
                // instead of the file containing just routes which is what we're
                // expecting.  In this case, we ignore this error.
                //
                CMTRACE("html string found error ignored because Dont_Require_URL is set");
                hr = S_OK;
            }
            else
            {
                CMTRACE1("SetRoutes: adding routes from URL failed with %x", hr);
                goto Cleanup;
            }
        }
    }

    //
    //  Delete default gateway
    //
    MYDBGASSERT(S_OK == hr);
    if (fDeleteDefaultGatewayViaFile || fDeleteDefaultGatewayViaURL)
    {
        hr = DeleteDefaultGateway(pGateway);
    }

Cleanup:
    //
    //  cleanup and leave
    //
    if (pRouteTable)
    {
        VirtualFree(pRouteTable, 0, MEM_RELEASE);
    }

    FreeIpHlpApis();

    CMTRACEHR("SetRoutes", hr);
    return hr;
}

//+----------------------------------------------------------------------------
//
// Func:    GetNextToken
//
// Desc:    utility function for parsing the argument string.  Goes past leading
//          whitespace and extracts a string
//
// Args:    [pszStart] - IN  the argument string
//          [ppszEnd]  - OUT where parsing for this arg ended
//          [pszOut]   - INOUT array of size MAX_PATH to hold arg if found
//
// Return:  BOOL, TRUE if another arg found, FALSE if not
//
// Notes:   
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
BOOL
GetNextToken(LPSTR pszStart, LPSTR * ppszEnd, LPSTR pszOut)
{
    MYDBGASSERT(pszStart);
    MYDBGASSERT(ppszEnd);

    LPSTR pszEnd = NULL;

    // clear leading white space
    while (isspace(*pszStart))
    {
        pszStart++;
    }

    if (NULL == *pszStart)
    {
        // just white space, no arg
        return FALSE;
    }

    //
    //  If this character is ", this is probably a quoted string, containing spaces.
    //  In this case, the termination character is another ".  Otherwise, assume
    //  it is a regular string terminated by a space.
    //
    if ('"' == *pszStart)
    {
        // may be a string containing spaces.
        pszEnd = strchr(pszStart + 1, '"');
    }

    if (NULL == pszEnd)
    {
        //
        //  Either it's a regular string, or we couldn't find a terminating " char
        //  so we fall back on space-delimited handling.
        //
        pszEnd = pszStart + 1;
        while (*pszEnd && !isspace(*pszEnd))
        {
            pszEnd++;
        }
        pszEnd--;
    }

    UINT cLen = (UINT)(pszEnd - pszStart + 1);

    if (cLen + 1 > MAX_PATH)
    {
        return FALSE;
    }
    else
    {
        lstrcpyn(pszOut, pszStart, cLen + 1);
        *ppszEnd = ++pszEnd;
        return TRUE;
    }
}


//+----------------------------------------------------------------------------
//
// Func:    Initialize
//
// Desc:    Initialization function, gets the route table and default gateway
//
// Args:    [ppmibRouteTable] - return location for route table
//          [ppGateway] - return location for default gateway
//
// Return:  HRESULT
//
// Notes:   
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
HRESULT
Initialize(
    OUT PMIB_IPFORWARDTABLE * ppmibRouteTable,
    OUT PMIB_IPFORWARDROW * ppGateway)
{
    HRESULT hr = S_OK;

    MYDBGASSERT(ppmibRouteTable);
    MYDBGASSERT(ppGateway);

    if (NULL == ppmibRouteTable || NULL == ppGateway)
    {
        return E_INVALIDARG;
    }

    hr = GetRouteTable(ppmibRouteTable);

    if (S_OK == hr)
    {
        MYDBGASSERT(*ppmibRouteTable);
        *ppGateway = GetDefaultGateway(*ppmibRouteTable);
    }

    CMTRACEHR("Initialize", hr);
    return hr;
}


//+----------------------------------------------------------------------------
//
// Func:    ParseArgs
//
// Desc:    convert the argument list into flags for our use.
//
// Args:    [pszArgList] - IN, the argument list
//          [the rest]   - OUT, all the arg values returned
//
// Return:  HRESULT
//
// Notes:   
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
HRESULT
ParseArgs(
    LPSTR pszArgList,
    BOOL * pfUseFile,
    LPSTR pszRoutesFile,
    BOOL * pfUseURL,
    LPSTR pszRoutesURL,
    BOOL * pfProfile,
    LPSTR pszProfile,
    BOOL * pfURLNotFoundIgnorable,
    BOOL * pfKeepTempFiles)
{
    HRESULT hr = S_OK;
    char    szArg[MAX_PATH];

    //
    //  verify arguments
    //
    if (NULL == pszArgList || 0 == lstrlen(pszArgList) ||
        !pfUseFile || !pszRoutesFile || !pfUseURL || !pszRoutesURL ||
        !pfProfile || !pszProfile ||
        !pfURLNotFoundIgnorable ||
        !pfKeepTempFiles)

    {
        return E_INVALIDARG;
    }

    CMTRACE1("ParseArgs: arg list is %s", pszArgList);

    //
    //  set the defaults
    //
    *pfUseFile = *pfUseURL = *pfProfile = *pfURLNotFoundIgnorable = FALSE;

    //
    //  process the Arglist
    //
    while (GetNextToken(pszArgList, &pszArgList, szArg))
    {
        if (0 == lstrcmpi("/Static_File_Name", szArg))
        {
            *pfUseFile = TRUE;

            if (!GetNextToken(pszArgList, &pszArgList, szArg))
            {
                return E_INVALIDARG;
            }

            if (lstrlen(szArg) > MAX_PATH)
            {
                CMTRACE("ParseArgs: file name is bigger than MAX_PATH!!");
                return E_INVALIDARG;
            }

            StrCpyWithoutQuotes(pszRoutesFile, szArg);
        }
        else if (0 == lstrcmpi("/Dont_Require_URL", szArg))
        {
            *pfURLNotFoundIgnorable = TRUE;
        }
        else if (0 == lstrcmpi("/URL_Update_Path", szArg))
        {
            *pfUseURL = TRUE;

            if (!GetNextToken(pszArgList, &pszArgList, szArg))
            {
                return E_INVALIDARG;
            }
            if (lstrlen(szArg) > MAX_PATH)
            {
                CMTRACE("ParseArgs: URL name is bigger than MAX_PATH!!");
                return E_INVALIDARG;
            }

            lstrcpy(pszRoutesURL, szArg);
        }
        else if (0 == lstrcmpi("/Profile", szArg))
        {
            *pfProfile = TRUE;

            if (!GetNextToken(pszArgList, &pszArgList, szArg))
            {
                return E_INVALIDARG;
            }
            if (lstrlen(szArg) > MAX_PATH)
            {
                CMTRACE("ParseArgs: Profile filename is bigger than MAX_PATH!!");
                return E_INVALIDARG;
            }

            StrCpyWithoutQuotes(pszProfile, szArg);
        }
        else if (0 == lstrcmpi("/No_Delete", szArg))
        {
            *pfKeepTempFiles = TRUE;
        }
        else
        {
            CMTRACE1("Cmroute: unrecognized parameter - %s", szArg);
            MYDBGASSERT("Cmroute - unrecognized parameter!!");
        }
    }

    CMTRACEHR("ParseArgs", hr);
    return hr;
}


#if 0

//  2000/11/28 SumitC
//  It wasn't clear what the required action should be when IP forwarding was
//  detected (should the connection be dropped or not) and it is a little late
//  to add UI to Whistler.  The 'Check IP forwarding' feature is thus removed.
//
// see Windows Db bug # 216558 for more details.

//+----------------------------------------------------------------------------
//
// Func:    CheckIPForwarding
//
// Desc:    checks to see if anything is enabled on the client machine that would
//          make us want to have cmroute not do anything
//
// Args:    none
//
// Return:  HRESULT
//
// Notes:   
//
// History: 01-Nov-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
HRESULT
CheckIPForwarding()
{
    HRESULT     hr = S_OK;
    MIB_IPSTATS stats;

    if (NO_ERROR != GetIpStatistics(&stats))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
    else
    {
        if (stats.dwForwarding)
        {
            hr = S_FALSE;
        }
    }

    CMTRACEHR("CheckIPForwarding", hr);
    return hr;
}
#endif

//+----------------------------------------------------------------------------
//
// Func:    GetRoutesFromFile
//
// Desc:    extracts the contents of the given file
//
// Args:    [pszFileName]          - IN, filename
//          [pszProfile]           - IN, profile if available
//          [ppszRouteInfo]        - OUT, the route table bytes
//          [pcbRouteInfo]         - OUT, the route table size
//
// Return:  HRESULT
//
// Notes:   
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
HRESULT
GetRoutesFromFile(
    LPSTR pszFileName,
    LPSTR pszProfile,
    LPSTR * ppszRouteInfo,
    DWORD * pcbRouteInfo)
{
    HRESULT hr = S_OK;
    HANDLE  hFile = NULL;
    LPSTR   psz = NULL;
    DWORD   cb = 0;
    BOOL    fRet;
    BY_HANDLE_FILE_INFORMATION info;

    MYDBGASSERT(pszFileName);
    MYDBGASSERT(ppszRouteInfo);
    MYDBGASSERT(pcbRouteInfo);

    if (NULL == pszFileName || NULL == ppszRouteInfo || NULL == pcbRouteInfo)
    {
        hr = E_INVALIDARG;
        goto Cleanup;
    }

    CMTRACE1("GetRoutesFromFile: filename is %s", pszFileName);

    //
    //  open the file, and read its contents into a buffer
    //
    hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        //
        //  perhaps the full pathname for the routes file wasn't specified.  If
        //  a Profile was passed in, we extract the ServiceDir and try again,
        //  using the ServiceDir as the path.
        //
        if (VerifyProfileAndGetServiceDir(pszProfile))
        {
            char sz[2 * MAX_PATH + 1];

            lstrcpy(sz, pszProfile);
            lstrcat(sz, pszFileName);

            CMTRACE1("GetRoutesFromFile: retrying with %s", sz);

            hFile = CreateFile(sz, GENERIC_READ, FILE_SHARE_READ, NULL,
                               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        }

        if (INVALID_HANDLE_VALUE == hFile)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto Cleanup;
        }
    }

    if (FALSE == GetFileInformationByHandle(hFile, &info))
    {
        hr = E_FAIL;
        goto Cleanup;
    }

    if (0 == info.nFileSizeLow)
    {
        CMTRACE("Routes file is EMPTY!!");
        hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
        goto Cleanup;
    }

    psz = (LPSTR) VirtualAlloc(NULL, info.nFileSizeLow, MEM_COMMIT, PAGE_READWRITE);
    if (NULL == psz)
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }

    fRet = ReadFile(hFile, psz, info.nFileSizeLow, &cb, NULL);
    if (FALSE == fRet)
    {
        hr = E_FAIL;
        goto Cleanup;
    }

    // success

    *ppszRouteInfo = psz;
    *pcbRouteInfo = cb;

Cleanup:
    if (hFile)
    {
        CloseHandle(hFile);
    }
    if (S_OK != hr)
    {
        VirtualFree(psz, 0, MEM_RELEASE);
    }

    CMTRACEHR("GetRoutesFromFile", hr);
    return hr;
}


//+----------------------------------------------------------------------------
//
// Func:    GetRoutesFromURL
//
// Desc:    extracts the contents of the given URL
//
// Args:    [pszURL]               - IN, the URL
//          [fKeepTempFiles]       - IN, do not delete temp buffer file(s)
//          [ppszRouteInfo]        - OUT, the route table bytes
//          [pcbRouteInfo]         - OUT, the route table size
//
// Return:  HRESULT
//
// Notes:   
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
HRESULT
GetRoutesFromURL(
    LPSTR pszURL,
    BOOL fKeepTempFiles,
    LPSTR * ppszRouteInfo,
    DWORD * pcbRouteInfo)
{
    HRESULT     hr = S_OK;
    HINTERNET   hInternet = NULL;
    HINTERNET   hPage = NULL;
    LPBYTE      pb = NULL;
    DWORD       cb = 0;
    TCHAR       szLocalBufferFile[MAX_PATH + 1];
    DWORD       cchLocalBuffer = 0;
    LPTSTR      pszLocalBuffer = NULL;
    FILE *      fp = NULL;
    BYTE        Buffer[1024];
    DWORD       dwRead;

    MYDBGASSERT(pszURL);
    MYDBGASSERT(ppszRouteInfo);
    MYDBGASSERT(pcbRouteInfo);

    if (NULL == pszURL || NULL == ppszRouteInfo || NULL == pcbRouteInfo)
    {
        hr = E_INVALIDARG;
        goto Cleanup;
    }

    CMTRACE1("GetRoutesFromURL: URL is %s", pszURL);

    //
    //  Get the path to the temp dir, if there is one.
    //
    cchLocalBuffer = GetTempPath(0, NULL);
    if (0 == cchLocalBuffer)
    {
        DWORD dwErr = GetLastError();
        CMTRACE1(TEXT("GetTempPath failed, using current dir, GLE=%d"), dwErr);
    }
    else
    {
        cchLocalBuffer += (lstrlen(TEXT("\\")) + lstrlen(szLocalBufferFile) + 1);

        pszLocalBuffer = (LPTSTR) VirtualAlloc(NULL,
                                               cchLocalBuffer * sizeof(TCHAR),
                                               MEM_COMMIT,
                                               PAGE_READWRITE);
        if (NULL == pszLocalBuffer)
        {
            hr = E_OUTOFMEMORY;
            CMTRACE(TEXT("GetRoutesFromURL - VirtualAlloc failed"));
            goto Cleanup;
        }

        if (0 == GetTempPath(cchLocalBuffer, pszLocalBuffer))
        {
            DWORD dwErr = GetLastError();
            CMTRACE1(TEXT("GetTempPath 2nd call failed, GLE=%d"), GetLastError());
            hr = HRESULT_FROM_WIN32(dwErr);
            goto Cleanup;
        }
    }

    //
    //  Get a name for the temp file (using the temp path if there is one)
    //
    if (0 == GetTempFileName(pszLocalBuffer ? pszLocalBuffer : TEXT("."),
                             TEXT("CMR"),
                             0,
                             szLocalBufferFile))
    {
        DWORD dwErr = GetLastError();
        CMTRACE1(TEXT("GetTempFileName failed, GLE=%d"), dwErr);
        hr = HRESULT_FROM_WIN32(dwErr);
        goto Cleanup;
    }

    //
    //  Open the temp file, and proceed.
    //
    fp = fopen(szLocalBufferFile, "w+b");
    if (NULL == fp)
    {
        CMTRACE1(TEXT("fopen failed(%s)"), szLocalBufferFile);
        hr = E_FAIL;
        goto Cleanup;
    }

    //
    //  Initialize WININET
    //
    hInternet = InternetOpen(TEXT("RouteMan"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    if (NULL == hInternet)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        CMTRACE1(TEXT("InternetOpen failed with 0x%x"), hr);
        goto Cleanup;
    }

    //
    //  Open the URL
    //
    hPage = InternetOpenUrl(hInternet, pszURL, NULL, 0, 0, 0);

    if (NULL == hPage)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        CMTRACE1(TEXT("InternetOpenUrl failed with 0x%x"), hr);
        goto Cleanup;
    }

    //
    //  Read the entire URL contents into the tempfile
    //
    do
    {
        if (!InternetReadFile(hPage, Buffer, sizeof(Buffer), &dwRead))
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            CMTRACE1(TEXT("InternetReadFile failed with 0x%x"), hr);
            goto Cleanup;
        }

        if (fwrite(Buffer, sizeof(BYTE), dwRead, fp) != dwRead)
        {
            CMTRACE1(TEXT("write failed to %s"), pszLocalBuffer);
            hr = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
            goto Cleanup;
        }

        cb += dwRead;

#if 0
        // ISSUE-2000/07/21-SumitC Code seems strange but might need it later
        //
        // Vijay/Andrew's original code has this, but is this correct?
        // The doc for InternetReadFile says this is just an EOF, if we
        // are to handle this case at all, we should just break;
        if (!dwRead)
            goto Cleanup;
#endif        
    }
    while (dwRead == 1024);

    hr = S_OK;

    if (fseek(fp, SEEK_SET, 0) != 0)
    {
        hr = E_FAIL;
        goto Cleanup;
    }

    pb = (LPBYTE) VirtualAlloc(NULL, cb, MEM_COMMIT, PAGE_READWRITE);
    if (NULL == pb)
    {
        hr = E_OUTOFMEMORY;
        goto Cleanup;
    }

    if (fread(pb, sizeof(BYTE), cb, fp) != cb)
    {
        hr = E_FAIL;
        goto Cleanup;
    }

    // success

    *ppszRouteInfo = (LPSTR) pb;
    *pcbRouteInfo = cb;

Cleanup:

    if (fp)
    {
        fclose(fp);
    }
    if (FALSE == fKeepTempFiles)
    {
        remove(szLocalBufferFile);
    }

    if (pszLocalBuffer && cchLocalBuffer)
    {
        VirtualFree(pszLocalBuffer, 0, MEM_RELEASE);
    }

    if (hPage)
    {
        InternetCloseHandle(hPage);
    }
    
    if (hInternet)
    {
        InternetCloseHandle(hInternet);
    }

    if (S_OK != hr)
    {
        VirtualFree(pb, 0, MEM_RELEASE);
    }

    CMTRACEHR("GetRoutesFromURL", hr);
    return hr;
}


//+----------------------------------------------------------------------------
//
// Func:    ProcessRouteInfo
//
// Desc:    Parses the given route table and modifies the real routetable accordingly
//
// Args:    [pszNewRouteInfo] - IN, bytes of route table to parse and add to the real one
//          [cbNewRouteInfo]  - IN, size of routetable
//          [pmibRouteTable]  - IN, real route table
//          [pGateway]        - IN, default gateway
//          [pfDeleteGateway] - OUT, does the route file say to delete default gateway?
//
// Return:  HRESULT (E_INVALIDARG, E_UNEXPECTED - for html file, etc)
//
// Notes:   
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
HRESULT
ProcessRouteInfo(
    const LPSTR pszNewRouteInfo,
    DWORD cbNewRouteInfo,
    PMIB_IPFORWARDTABLE pmibRouteTable,
    PMIB_IPFORWARDROW pGateway,
    BOOL * pfDeleteDefaultGateway)
{
    HRESULT hr = S_OK;
    DWORD   cLines = 0;
    char    szBuf[MAX_PATH];
    LPSTR   pszNextLineToProcess;

    MYDBGASSERT(pszNewRouteInfo);
    MYDBGASSERT(cbNewRouteInfo);
    MYDBGASSERT(pmibRouteTable);
    MYDBGASSERT(pGateway);
    MYDBGASSERT(pfDeleteDefaultGateway);

    if (!pszNewRouteInfo || !cbNewRouteInfo || !pmibRouteTable || !pGateway || !pfDeleteDefaultGateway)
    {
        return E_INVALIDARG;
    }

    if ((NULL == g_pfnCreateIpForwardEntry) || (NULL == g_pfnDeleteIpForwardEntry))
    {
        return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
    }

    *pfDeleteDefaultGateway = FALSE;

    // Make sure WININET zero terminates this....
    pszNewRouteInfo[cbNewRouteInfo] = '\0';

    //
    //  Convert string to lower
    //
    CharLowerA(pszNewRouteInfo);

    //
    //  sanity checks (in the URL case, if the route file isn't found the server
    //  is likely to return an HTML file to indicate 404 - file not found.)
    //
    if (strstr(pszNewRouteInfo, "<html"))
    {
        CMTRACE("html string found - invalid route file\n");
        hr = E_UNEXPECTED;
        goto Cleanup;
    }

    //
    //  for each line
    //
    for (;;)
    {
        DWORD               ipDest, ipMask, ipGateway, ipMetric;
        DWORD               dwIf = -1;
        DWORD               dwParam;
        LPSTR               psz;        // temp var to hold each line as we process it
        MIB_IPFORWARDROW    route;

        enum { VERB_ADD, VERB_DELETE } eVerb;

        //
        //  Per strtok syntax, use pszNewRouteInfo the first time, and NULL thereafter
        //
        psz = strtok(((0 == cLines) ? pszNewRouteInfo : NULL), "\n\0");
        if (NULL == psz)
            break;

        ++cLines;

        //
        //  All errors within the for statement are due to bad data within the file
        //
        hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);

        //
        //  PART 1 : add/delete, followed by the IP address, or remove_gateway
        //
        if (FALSE == GetNextToken(psz, &psz, szBuf))
        {
            CMTRACE1("ProcessRouteInfo [%d] didn't find add/delete which is required", cLines);
            goto Cleanup;
        }

        if (0 == lstrcmpi(szBuf, "add"))
        {
            eVerb = VERB_ADD;
        }
        else if (0 == lstrcmpi(szBuf, "delete"))
        {
            eVerb = VERB_DELETE;
        }
        else if (0 == lstrcmpi(szBuf, "remove_gateway"))
        {
            *pfDeleteDefaultGateway = TRUE;
            hr = S_OK;
            // ignore the rest of the line
            continue;
        }
        else
        {
            CMTRACE2("ProcessRouteInfo [%d] found unexpected string %s instead of add/delete/remove_gateway", cLines, szBuf);
            goto Cleanup;
        }

        if (FALSE == GetNextToken(psz, &psz, szBuf))
        {
            CMTRACE1("ProcessRouteInfo [%d] dest ip required for add/delete, and is missing", cLines);
            goto Cleanup;
        }
        
        if (FALSE == ConvertSzToIP(szBuf, ipDest))
        {
            CMTRACE2("ProcessRouteInfo [%d] required ip address/mask %s has bad format", cLines, szBuf);
            goto Cleanup;
        }

        //
        //  PART 2 : mask, followed by the IP address (NOT REQUIRED)
        //
        if (FALSE == GetNextToken(psz, &psz, szBuf))
        {
            CMTRACE1("ProcessRouteInfo [%d] ends too early after add/delete", cLines);
            goto Cleanup;
        }

        if (0 == lstrcmpi(szBuf, "mask"))
        {
            if (FALSE == GetNextToken(psz, &psz, szBuf))
            {
                CMTRACE1("ProcessRouteInfo [%d] ip required for mask, and is missing", cLines);
                goto Cleanup;
            }

            if (FALSE == ConvertSzToIP(szBuf, ipMask))
            {
                CMTRACE2("ProcessRouteInfo [%d] required ip address/mask %s has bad format", cLines, szBuf);
                goto Cleanup;
            }

            if (FALSE == GetNextToken(psz, &psz, szBuf))
            {
                CMTRACE1("ProcessRouteInfo [%d] ends too early after mask", cLines);
                goto Cleanup;
            }
        }
        else
        {
            CMTRACE1("ProcessRouteInfo [%d] didn't find \"mask\", that's ok, continuing", cLines);
            ipMask = (DWORD)-1;
        }

        //
        //  PART 3 : gateway (or "default")
        //
        if (0 == lstrcmpi(szBuf, "default"))
        {
            ipGateway = pGateway->dwForwardNextHop;
        }
        else
        {
            if (FALSE == ConvertSzToIP(szBuf, ipGateway))
            {
                CMTRACE2("ProcessRouteInfo [%d] bad format for gateway %s", cLines, szBuf);
                goto Cleanup;
            }
        }

        //
        //  PART 4 : metric, followed by a number (REQUIRED)
        //
        if (FALSE == GetNextToken(psz, &psz, szBuf))
        {
            CMTRACE1("ProcessRouteInfo [%d] didn't find \"metric\" which is required", cLines);
            goto Cleanup;
        }

        if (0 == lstrcmpi(szBuf, "metric"))
        {
            if (FALSE == GetNextToken(psz, &psz, szBuf))
            {
                CMTRACE1("ProcessRouteInfo [%d] number value after \"metric\" missing", cLines);
                goto Cleanup;
            }

            if (0 == lstrcmpi(szBuf, "default"))
            {
                ipMetric = pGateway->dwForwardMetric1;
            }
            else
            {
                if (FALSE == ConvertSzToIP(szBuf, ipMetric))
                {
                    CMTRACE2("ProcessRouteInfo [%d] required ip metric %s has bad format", cLines, szBuf);
                    goto Cleanup;
                }

/*
#if 0
                dwParam = sscanf(szBuf, "%d", &ipMetric);
                if (0 == dwParam)
                {
                    CMTRACE2("ProcessRouteInfo [%d] bad format for metric value - %s", cLines, szBuf);
                    goto Cleanup;
                }
#endif
*/
            }
        }
        else
        {
            CMTRACE2("ProcessRouteInfo [%d] found unexpected string %s instead of \"metric\"", cLines, szBuf);
            goto Cleanup;
        }

        //
        //  PART 5 : if (the interface), followed by a number (REQUIRED)
        //
        if (FALSE == GetNextToken(psz, &psz, szBuf))
        {
            CMTRACE1("ProcessRouteInfo [%d] didn't find \"if\" which is required", cLines);
            goto Cleanup;
        }

        if (0 == lstrcmpi(szBuf, "if"))
        {
            if (FALSE == GetNextToken(psz, &psz, szBuf))
            {
                CMTRACE1("ProcessRouteInfo [%d] number value after \"if\" missing", cLines);
                goto Cleanup;
            }

            if (0 == lstrcmpi(szBuf, "default"))
            {
                dwIf = pGateway->dwForwardIfIndex;
            }
            else
            {
                dwParam = sscanf(szBuf, "%d", &dwIf);
                if (0 == dwParam)
                {
                    CMTRACE2("ProcessRouteInfo [%d] bad format for if value - %s", cLines, szBuf);
                    goto Cleanup;
                }
            }
        }
        else
        {
            CMTRACE2("ProcessRouteInfo [%d] found unexpected string %s instead of \"if\"", cLines, szBuf);
            goto Cleanup;
        }

        //
        //  Run the verb (add or delete)
        //
        ZeroMemory(&route, sizeof(route));

        route.dwForwardDest      = ipDest;
        route.dwForwardIfIndex   = dwIf;
        route.dwForwardMask      = ipMask;
        route.dwForwardMetric1   = ipMetric;
        route.dwForwardNextHop   = ipGateway;

        route.dwForwardPolicy    = 0;
        route.dwForwardNextHopAS = 0;
        route.dwForwardType      = 3;
        route.dwForwardProto     = 3;
        route.dwForwardAge       = INFINITE;
        route.dwForwardMetric2   = 0xFFFFFFFF;
        route.dwForwardMetric3   = 0xFFFFFFFF;
        route.dwForwardMetric4   = 0xFFFFFFFF;
        route.dwForwardMetric5   = 0xFFFFFFFF;

        // ISSUE-2000/07/21-SumitC Can we ever really get here in the code with dwIf == -1 ?
        //
        // Check that the interface was specified
        if (-1 == dwIf)
        {
            // Nope, lets go pick one
            dwIf = GetIf(route, *pmibRouteTable);
        }

        DWORD dwRet = 0;

        switch (eVerb)
        {
        case VERB_ADD:
            dwRet = g_pfnCreateIpForwardEntry(&route);
            if (ERROR_SUCCESS != dwRet)
            {
                CMTRACE2("ProcessRouteInfo [%d] CreateIpForwardEntry failed with %d", cLines, dwRet);
                hr = HRESULT_FROM_WIN32(dwRet);
                goto Cleanup;
            }
            hr = S_OK;
            break;
        case VERB_DELETE:
            dwRet = g_pfnDeleteIpForwardEntry(&route);
            if (ERROR_SUCCESS != dwRet)
            {
                CMTRACE2("ProcessRouteInfo [%d] DeleteIpForwardEntry failed with %d", cLines, dwRet);
                hr = HRESULT_FROM_WIN32(dwRet);
                goto Cleanup;
            }
            hr = S_OK;
            break;
        default:
            CMTRACE("ProcessRouteInfo [%d] Unsupported route command, add or delete only");
            MYDBGASSERT(0);
            hr = E_FAIL;
            break;
        }
    }

Cleanup:

    CMTRACEHR("ProcessRouteInfo", hr);
    return hr;
}


//+----------------------------------------------------------------------------
//
// Func:    DeleteDefaultGateway
//
// Desc:    Deletes the default routing gateway for this system
//
// Args:    [pGateway] - the gateway
//
// Return:  HRESULT
//
// Notes:   This should be the last function called within CMroute, and is called
//          only if all other processing succeeded.
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
HRESULT
DeleteDefaultGateway(PMIB_IPFORWARDROW pGateway)
{
    CMTRACE("DeleteDefaultGateway: entering");

    HRESULT hr = S_OK;
    DWORD dwErr = 0;

    if (NULL == g_pfnDeleteIpForwardEntry)
    {
        hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
        goto Cleanup;    
    }

    dwErr = g_pfnDeleteIpForwardEntry(pGateway);

    if (NO_ERROR != dwErr)
    {
        hr = HRESULT_FROM_WIN32(dwErr);
        CMTRACE1("DeleteDefaultGateway failed with error %x", dwErr);
    }

Cleanup:
    CMTRACEHR("DeleteDefaultGateway", hr);
    return hr;
}


//+----------------------------------------------------------------------------
//
// Func:    IsValidIPAddress
//
// Desc:    Checks to see if given string can be a valid IP address
//
// Args:    [sz] - IN, the string
//
// Return:  BOOL, FALSE means invalid chars were found
//
// History: 20-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
BOOL
IsValidIPAddress(LPSTR sz)
{
    MYDBGASSERT(sz);

    while ((*sz) && (!isspace(*sz)))
    {
        if ((*sz >= '0') && (*sz <= '9'))
            ;
        else if ((*sz == '.') || (*sz == '*') || (*sz == '?'))
            ;
        else
        {
            CMTRACE1(TEXT("IsValidIPAddress failed on %s\n"), sz);
            MYDBGASSERT("IsValidIPAddress");
            return FALSE;
        }
        ++sz;
    }

    return TRUE;
}


//+----------------------------------------------------------------------------
//
// Func:    ConvertSzToIP
//
// Desc:    Converts the given string into a DWORD representing an IP address
//
// Args:    [sz]   - IN, string to convert
//          [dwIP] - OUT BYREF, dword for IP address
//
// Return:  BOOL, FALSE if conversion failed (usually means bad format)
//
// History: 12-Mar-2000   SumitC      Created
//
//-----------------------------------------------------------------------------
BOOL
ConvertSzToIP(LPSTR sz, DWORD& dwIP)
{
    DWORD dwParams, d1, d2, d3, d4;

    if (FALSE == IsValidIPAddress(sz))
    {
        return FALSE;
    }

    dwParams = sscanf(sz, "%d.%d.%d.%d", &d1, &d2, &d3, &d4);

    if (0 == dwParams)
    {
        MYDBGASSERT("ConvertSzToIP - bad format for IP address");
        return FALSE;
    }
    else if (1 == dwParams)
    {
        dwIP = d1 | 0xffffff00;
    }
    else if (2 == dwParams)
    {
        dwIP = d1 | (d2 << 8) | 0xffff0000;
    }
    else if (3 == dwParams)
    {
        dwIP = d1 | (d2 << 8) | (d3 << 16) | 0xff000000;
    }
    else
    {
        dwIP = d1 | (d2 << 8) | (d3 << 16) | (d4 << 24);
    }

    return TRUE;
}


//+----------------------------------------------------------------------------
//
// Func:    GetRouteTable
//
// Desc:    Same as GetIpForwardTable but alloc's the table for you.
//          VirtualFree() the buffer returned.
//
// Args:    [ppTable] - OUT, returned route table
//
// Return:  HRESULT
//
// Notes:   ppTable should be VirtualFree'd by caller.
//
// History: 24-Feb-1999   AnBrad      Created
//          22-Mar-2000   SumitC      Rewrote
//
//-----------------------------------------------------------------------------
HRESULT
GetRouteTable(PMIB_IPFORWARDTABLE * ppTable)
{
    DWORD               dwErr = NO_ERROR;
    DWORD               cbbuf = 0;
    PMIB_IPFORWARDTABLE p = NULL;
    HRESULT             hr = S_OK;

    MYDBGASSERT(ppTable);

    //
    //  Make sure we have a function pointer for GetIpForwardTable
    //
    if (NULL == g_pfnGetIpForwardTable)
    {
        hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
        goto Cleanup;
    }

    //
    //  Get the route table
    //
    dwErr = g_pfnGetIpForwardTable(NULL, &cbbuf, FALSE);

    if (ERROR_INSUFFICIENT_BUFFER != dwErr)
    {
        // hmm, a real error
        hr = HRESULT_FROM_WIN32(ERROR_UNEXP_NET_ERR);
        goto Cleanup;
    }
    else
    {
        p = (PMIB_IPFORWARDTABLE) VirtualAlloc(NULL, cbbuf, MEM_COMMIT, PAGE_READWRITE);

        if (!p)
        {
            hr = E_OUTOFMEMORY;
            goto Cleanup;
        }

        if (g_pfnGetIpForwardTable(p, &cbbuf, TRUE))
        {
            hr = E_FAIL;
            goto Cleanup;
        }

        *ppTable = p;
    }    

Cleanup:
    if (S_OK != hr)
    {
        if (p)
        {
            VirtualFree(p, 0, MEM_RELEASE);
        }
    }

    CMTRACEHR("GetRouteTable", hr);
    return hr;
}

//+----------------------------------------------------------------------------
//
// Func:    GetIf
//
// Desc:    Find the interface for a route
//
// Args:    [route]      - IN, route for which we need the Interface
//          [RouteTable] - IN, route table
//
// Return:  DWORD which is the IF
//
// Notes:   Logic stolen from \nt\private\net\sockets\strmtcp\route
//
// History: 24-Feb-1999   AnBrad      Created
//          22-Mar-2000   SumitC      Cleaned up
//
//-----------------------------------------------------------------------------
DWORD
GetIf(const MIB_IPFORWARDROW& route, const MIB_IPFORWARDTABLE& RouteTable)
{
    for(DWORD dwIndex = 0; dwIndex < RouteTable.dwNumEntries; dwIndex++)
    {
        const MIB_IPFORWARDROW& Row = RouteTable.table[dwIndex];
        
        if (Row.dwForwardMask && 
            (Row.dwForwardDest & Row.dwForwardMask) == 
            (route.dwForwardNextHop & Row.dwForwardMask))
        {
            return Row.dwForwardIfIndex;
        }         
    }

    return 0;
}


//+----------------------------------------------------------------------------
//
// Func:    GetDefaultGateway
//
// Desc:    Find the default gateway
//
// Args:    [pRouteTable] - IN, the route table (IP forward table)
//
// Return:  PMIB_IPFORWARDROW the row of the gateway (a ptr within pRouteTable)
//
// Notes:   Do not free returned value
//
// History: 24-Feb-1999   AnBrad      Created
//          22-Mar-2000   SumitC      Cleaned up
//
//-----------------------------------------------------------------------------
PMIB_IPFORWARDROW
GetDefaultGateway(PMIB_IPFORWARDTABLE pRouteTable)
{
    PMIB_IPFORWARDROW pRow, pDefGateway;

    // Cycle thru the entire table & find the gateway with the least metric
    pDefGateway = NULL;
    for(pRow = pRouteTable->table; pRow != pRouteTable->table + pRouteTable->dwNumEntries; ++pRow)
    {
        if (pRow->dwForwardDest == 0)
        {
            if (pDefGateway == NULL)
            {
                pDefGateway = pRow;
            }
            else
            {
                if (pRow->dwForwardMetric1 == pDefGateway->dwForwardMetric1 &&
                    pRow->dwForwardAge >= pDefGateway->dwForwardAge)
                {
                    pDefGateway = pRow;
                }
                
                if (pRow->dwForwardMetric1 < pDefGateway->dwForwardMetric1)
                {
                    pDefGateway = pRow;
                }
            }
        }
    }
    
    return pDefGateway;
}


//+----------------------------------------------------------------------------
//
// Func:    StrCpyWithoutQuotes
//
// Desc:    Wrapper for lstrcpy, which removes surrounding double quotes if necessary
//
// Args:    [pszDest] - OUT, destination for the copy
//          [pszSrc]  - OUT, source for the copy
//
// Return:  a ptr to pszDest, or NULL if failure.
//
// Notes:   
//
// History: 12-Apr-1999   SumitC    Created
//
//-----------------------------------------------------------------------------
LPSTR
StrCpyWithoutQuotes(LPSTR pszDest, LPCSTR pszSrc)
{
    MYDBGASSERT(pszDest);
    MYDBGASSERT(pszSrc);

    int len = lstrlen(pszSrc);

    if ((len > 2) && ('"' == pszSrc[0]) && ('"' == pszSrc[len - 1]))
    {
        return lstrcpyn(pszDest, &pszSrc[1], len - 1);
    }
    else
    {
        return lstrcpy(pszDest, pszSrc);
    }
}


//+----------------------------------------------------------------------------
//
// Func:    VerifyProfileAndGetServiceDir
//
// Desc:    Checks the given profile, and modifies it to produce the ServiceDir
//
// Args:    [pszProfile] - IN OUT, profile name, modified IN PLACE
//
// Return:  TRUE if modified pszProfile is now the ServiceDir, or FALSE if failure.
//
// Notes:   pszProfile is MODIFIED IN PLACE.
//
// History: 12-Apr-1999   SumitC    Created
//
//-----------------------------------------------------------------------------
BOOL
VerifyProfileAndGetServiceDir(IN OUT LPSTR pszProfile)
{
    HANDLE hFile = NULL;

    MYDBGASSERT(pszProfile);
    MYDBGASSERT(lstrlen(pszProfile));

    if ((NULL == pszProfile) || (lstrlen(pszProfile) < 4))
    {
        return FALSE;
    }

    //
    //  The profile string may be surrounded by double-quotes, if so remove them.
    //

    //
    //  First check to see if the profile really exists.  This also serves as
    //  verification for the existence of the directory.
    //
    hFile = CreateFile(pszProfile, GENERIC_READ, FILE_SHARE_READ, NULL,
                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        return FALSE;
    }
    else
    {
        CloseHandle(hFile);
    }

    //
    //  Now check to see that the file does indeed end in .CMP
    //

    LPSTR psz = pszProfile + lstrlen(pszProfile) - lstrlen(".CMP");

    if (0 != lstrcmpi(psz, ".CMP"))
    {
        return FALSE;
    }

    //
    //  The profile name is the same as the ServiceDir name, so we just terminate
    //  the string at the '.' and append a '\'
    //

    *psz = '\\';
    psz++;
    *psz = '\0';

    return TRUE;
}


#if DBG

LPWSTR wszType[] = {L"Other", 
                    L"Invalid",
                    L"Direct",
                    L"Indirect"};

LPWSTR wszProto[] ={L"Other",                           // MIB_IPPROTO_OTHER    1
                    L"Local",                           // MIB_IPPROTO_LOCAL    2
                    L"SNMP",                            // MIB_IPPROTO_NETMGMT  3
                    L"ICMP",                            // MIB_IPPROTO_ICMP     4
                    L"Exterior Gateway Protocol",       // MIB_IPPROTO_EGP      5
                    L"GGP",                             // MIB_IPPROTO_GGP      6
                    L"Hello",                           // MIB_IPPROTO_HELLO    7
                    L"Routing Information Protocol",    // MIB_IPPROTO_RIP      8
                    L"IS IS",                           // MIB_IPPROTO_IS_IS    9
                    L"ES IS",                           // MIB_IPPROTO_ES_IS    10
                    L"Cicso",                           // MIB_IPPROTO_CISCO    11
                    L"BBN",                             // MIB_IPPROTO_BBN      12
                    L"Open Shortest Path First",        // MIB_IPPROTO_OSPF     13
                    L"Border Gateway Protocol"};        // MIB_IPPROTO_BGP      14


//+----------------------------------------------------------------------
//
//  Function:   PrintRoute
//
//  Purpose:    Prints out a route from the IP forward table
//
//  Arguments:
//      pRow    [in]    the route
//
//  Returns:    zip
//
//  Author:     anbrad   24 Feb 1999
//
//  Notes:      
//
//-----------------------------------------------------------------------
void PrintRoute(PMIB_IPFORWARDROW pRow)
{
    TCHAR sz[MAX_PATH];

    wsprintf(sz, "dwDest = %s\n", IPtoTsz(pRow->dwForwardDest)); // IP addr of destination
    OutputDebugString(sz);
    wsprintf(sz, "dwMask = %s\n", IPtoTsz(pRow->dwForwardMask)); // subnetwork mask of destination
    OutputDebugString(sz);
    
    wsprintf(sz, "dwPolicy = %d\n"
            "dwNextHop = %s\n"
            "dwIfIndex = %d\n"
            "dwType = %s\n"
            "dwProto = %s\n"
            "dwAge = %d\n"
            "dwNextHopAS = %d\n",

    pRow->dwForwardPolicy,              // conditions for multi-path route
    IPtoTsz(pRow->dwForwardNextHop),    // IP address of next hop
    pRow->dwForwardIfIndex,             // index of interface
    wszType[pRow->dwForwardType-1],     // route type
    wszProto[pRow->dwForwardProto-1],   // protocol that generated route
    pRow->dwForwardAge,                 // age of route
    pRow->dwForwardNextHopAS);          // autonomous system number 
                                        // of next hop
    OutputDebugString(sz);

    if (MIB_IPROUTE_METRIC_UNUSED != pRow->dwForwardMetric1)
    {
        wsprintf(sz, "dwMetric1 = %d\n", pRow->dwForwardMetric1);
        OutputDebugString(sz);
    }

    if (MIB_IPROUTE_METRIC_UNUSED != pRow->dwForwardMetric2)
    {
        wsprintf(sz, "dwMetric2 = %d\n", pRow->dwForwardMetric2);
        OutputDebugString(sz);
    }

    if (MIB_IPROUTE_METRIC_UNUSED != pRow->dwForwardMetric3)
    {
        wsprintf(sz, "dwMetric3 = %d\n", pRow->dwForwardMetric3);
        OutputDebugString(sz);
    }

    if (MIB_IPROUTE_METRIC_UNUSED != pRow->dwForwardMetric4)
    {
        wsprintf(sz, "dwMetric4 = %d\n", pRow->dwForwardMetric4);
        OutputDebugString(sz);
    }

    if (MIB_IPROUTE_METRIC_UNUSED != pRow->dwForwardMetric5)
    {
        wsprintf(sz, "dwMetric5 = %d\n", pRow->dwForwardMetric5);
        OutputDebugString(sz);
    }

    wsprintf(sz, "\n");
    OutputDebugString(sz);
}


#if DBG
//+----------------------------------------------------------------------
//
//  Function:   PrintRouteTable
//
//  Purpose:    Does a "ROUTE PRINT" using the iphlpapi's
//
//  Arguments:  none
//
//  Returns:    zip
//
//  Author:     anbrad   24 Feb 1999
//
//  Notes:      
//
//-----------------------------------------------------------------------
void PrintRouteTable()
{
#define PAGE 4096
    
    BYTE        buf[PAGE];
    DWORD       cbbuf = sizeof(buf);
    TCHAR       sz[MAX_PATH];

    if (g_pfnGetIpForwardTable)
    {
        PMIB_IPFORWARDTABLE table = (PMIB_IPFORWARDTABLE)&buf;
    
        if (g_pfnGetIpForwardTable(table, &cbbuf, TRUE))
            return;

        wsprintf(sz, "\n\nFound %d routes\n", table->dwNumEntries);
        OutputDebugString(sz);

        for (DWORD d=0; d < table->dwNumEntries; ++d)
        {
            PrintRoute(table->table+d);
        }
    }
}
#endif


//+----------------------------------------------------------------------
//
//  Function:   IPtoTsz
//
//  Purpose:    Changes a dword to a "dotted string" notation
//
//  Arguments:
//      dw       [in]   IP address
//
//  Returns:    LPTSTR which is a static string
//
//  Author:     anbrad   24 Feb 1999
//
//  Notes:      Global makes this NOT thread safe, and it is not needed 
//              for cmroute.exe
//
//-----------------------------------------------------------------------

TCHAR tsz[20];


LPTSTR IPtoTsz(DWORD dw)
{
    wsprintf(tsz, TEXT("%03d.%03d.%03d.%03d"), 
                   (DWORD)LOBYTE(LOWORD(dw)),
                   (DWORD)HIBYTE(LOWORD(dw)),
                   (DWORD)LOBYTE(HIWORD(dw)),
                   (DWORD)HIBYTE(HIWORD(dw)));
    
    return tsz;
}

LPSTR IPtosz(DWORD dwIP, char *psz)
{
    wsprintfA(psz, ("%d.%d.%d.%d"), 
                   (DWORD)LOBYTE(LOWORD(dwIP)),
                   (DWORD)HIBYTE(LOWORD(dwIP)),
                   (DWORD)LOBYTE(HIWORD(dwIP)),
                   (DWORD)HIBYTE(HIWORD(dwIP)));
    
    return psz;
}
#endif // DBG