/*
 * PROJECT:     ReactOS ipconfig utility
 * LICENSE:     GPL - See COPYING in the top level directory
 * FILE:        apps/utils/net/ipconfig/ipconfig.c
 * PURPOSE:     Display IP info for net adapters
 * PROGRAMMERS: Copyright 2005 - 2006 Ged Murphy (gedmurphy@gmail.com)
 */
/*
 * TODO:
 * fix renew / release
 * implement flushdns, registerdns, displaydns, showclassid, setclassid
 * allow globbing on adapter names
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <time.h>
#include <iphlpapi.h>
#include "resource.h"

#define GUID_LEN 40

HINSTANCE hInstance;
HANDLE ProcessHeap;


LPTSTR GetNodeTypeName(UINT NodeType)
{
    static TCHAR szNode[14];

    switch (NodeType)
    {
        case 1:
            if (!LoadString(hInstance, IDS_BCAST, szNode, sizeof(szNode)))
                return NULL;
            break;

        case 2:
            if (!LoadString(hInstance, IDS_P2P, szNode, sizeof(szNode)))
                return NULL;
            break;

        case 4:
            if (!LoadString(hInstance, IDS_MIXED, szNode,  sizeof(szNode)))
                return NULL;
            break;

        case 8:
            if (!LoadString(hInstance, IDS_HYBRID, szNode,  sizeof(szNode)))
                return NULL;
            break;

        default :
            if (!LoadString(hInstance, IDS_UNKNOWN, szNode,  sizeof(szNode)))
                return NULL;
            break;
    }

    return szNode;
}


LPTSTR GetInterfaceTypeName(UINT InterfaceType)
{
    static TCHAR szIntType[25];

    switch (InterfaceType)
    {
        case MIB_IF_TYPE_OTHER:
            if (!LoadString(hInstance, IDS_OTHER, szIntType, sizeof(szIntType)))
                return NULL;
            break;

        case MIB_IF_TYPE_ETHERNET:
            if (!LoadString(hInstance, IDS_ETH, szIntType, sizeof(szIntType)))
                return NULL;
            break;

        case MIB_IF_TYPE_TOKENRING:
            if (!LoadString(hInstance, IDS_TOKEN, szIntType, sizeof(szIntType)))
                return NULL;
            break;

        case MIB_IF_TYPE_FDDI:
            if (!LoadString(hInstance, IDS_FDDI, szIntType, sizeof(szIntType)))
                return NULL;
            break;

        case MIB_IF_TYPE_PPP:
            if (!LoadString(hInstance, IDS_PPP, szIntType, sizeof(szIntType)))
                return NULL;
            break;

        case MIB_IF_TYPE_LOOPBACK:
            if (!LoadString(hInstance, IDS_LOOP, szIntType, sizeof(szIntType)))
                return NULL;
            break;

        case MIB_IF_TYPE_SLIP:
            if (!LoadString(hInstance, IDS_SLIP, szIntType, sizeof(szIntType)))
                return NULL;
            break;

        default:
            if (!LoadString(hInstance, IDS_UNKNOWN, szIntType, sizeof(szIntType)))
                return NULL;
            break;
    }

    return szIntType;
}


/* print MAC address */
PTCHAR PrintMacAddr(PBYTE Mac)
{
    static TCHAR MacAddr[20];

    _stprintf(MacAddr, _T("%02x-%02x-%02x-%02x-%02x-%02x"),
        Mac[0], Mac[1], Mac[2], Mac[3], Mac[4],  Mac[5]);

    return MacAddr;
}


VOID DoFormatMessage(LONG ErrorCode)
{
    LPVOID lpMsgBuf;
    //DWORD ErrorCode;

    if (ErrorCode == 0)
        ErrorCode = GetLastError();

    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                        FORMAT_MESSAGE_FROM_SYSTEM |
                        FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL,
                      ErrorCode,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
                      (LPTSTR) &lpMsgBuf,
                      0,
                      NULL))
    {
        _tprintf(_T("%s"), (LPTSTR)lpMsgBuf);
        LocalFree(lpMsgBuf);
    }
}


LPTSTR GetConnectionType(LPTSTR lpClass)
{
    HKEY hKey = NULL;
    LPTSTR ConType = NULL;
    TCHAR Path[256];
    LPTSTR PrePath  = _T("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\");
    LPTSTR PostPath = _T("\\Connection");
    DWORD PathSize;
    DWORD dwType;
    DWORD dwDataSize;

    /* don't overflow the buffer */
    PathSize = lstrlen(PrePath) + lstrlen(lpClass) + lstrlen(PostPath) + 1;
    if (PathSize >= 255)
        return NULL;

    wsprintf(Path, _T("%s%s%s"), PrePath, lpClass, PostPath);

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                     Path,
                     0,
                     KEY_READ,
                     &hKey) == ERROR_SUCCESS)
    {
        if(RegQueryValueEx(hKey,
                           _T("Name"),
                           NULL,
                           &dwType,
                           NULL,
                           &dwDataSize) == ERROR_SUCCESS)
        {
            ConType = (LPTSTR)HeapAlloc(ProcessHeap,
                                        0,
                                        dwDataSize);
            if (ConType == NULL)
                return NULL;

            if(RegQueryValueEx(hKey,
                               _T("Name"),
                               NULL,
                               &dwType,
                               (PBYTE)ConType,
                               &dwDataSize) != ERROR_SUCCESS)
            {
                ConType = NULL;
            }
        }
    }

    if (hKey != NULL)
        RegCloseKey(hKey);

    return ConType;
}


LPTSTR GetConnectionDescription(LPTSTR lpClass)
{
    HKEY hBaseKey = NULL;
    HKEY hClassKey = NULL;
    LPTSTR lpKeyClass = NULL;
    LPTSTR lpConDesc = NULL;
    LPTSTR lpPath = NULL;
    TCHAR szPrePath[] = _T("SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002bE10318}\\");
    DWORD dwType;
    DWORD dwDataSize;
    INT i;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                     szPrePath,
                     0,
                     KEY_READ,
                     &hBaseKey) != ERROR_SUCCESS)
    {
        return NULL;
    }

    for (i=0; ; i++)
    {
        DWORD PathSize;
        LONG Status;
        TCHAR szName[10];
        DWORD NameLen = 9;

        if ((Status = RegEnumKeyEx(hBaseKey,
                                   i,
                                   szName,
                                   &NameLen,
                                   NULL,
                                   NULL,
                                   NULL,
                                   NULL)) != ERROR_SUCCESS)
        {
            if (Status == ERROR_NO_MORE_ITEMS)
            {
                DoFormatMessage(Status);
                lpConDesc = NULL;
                goto CLEANUP;
            }
            else
                continue;
        }

        PathSize = lstrlen(szPrePath) + lstrlen(szName) + 1;
        lpPath = (LPTSTR)HeapAlloc(ProcessHeap,
                                   0,
                                   PathSize * sizeof(TCHAR));
        if (lpPath == NULL)
            goto CLEANUP;

        wsprintf(lpPath, _T("%s%s"), szPrePath, szName);

        //MessageBox(NULL, lpPath, NULL, 0);

        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                         lpPath,
                         0,
                         KEY_READ,
                         &hClassKey) != ERROR_SUCCESS)
        {
            goto CLEANUP;
        }

        HeapFree(ProcessHeap, 0, lpPath);
        lpPath = NULL;

        if(RegQueryValueEx(hClassKey,
                           _T("NetCfgInstanceId"),
                           NULL,
                           &dwType,
                           NULL,
                           &dwDataSize) == ERROR_SUCCESS)
        {
            lpKeyClass = (LPTSTR)HeapAlloc(ProcessHeap,
                                           0,
                                           dwDataSize);
            if (lpKeyClass == NULL)
                goto CLEANUP;

            if(RegQueryValueEx(hClassKey,
                               _T("NetCfgInstanceId"),
                               NULL,
                               &dwType,
                               (PBYTE)lpKeyClass,
                               &dwDataSize) != ERROR_SUCCESS)
            {
                lpKeyClass = NULL;
                HeapFree(ProcessHeap, 0, lpKeyClass);
                continue;
            }
        }
        else
            continue;

        if (!lstrcmp(lpClass, lpKeyClass))
        {
            HeapFree(ProcessHeap, 0, lpKeyClass);
            lpKeyClass = NULL;

            if(RegQueryValueEx(hClassKey,
                               _T("DriverDesc"),
                               NULL,
                               &dwType,
                               NULL,
                               &dwDataSize) == ERROR_SUCCESS)
            {
                lpConDesc = (LPTSTR)HeapAlloc(ProcessHeap,
                                              0,
                                              dwDataSize);
                if (lpConDesc == NULL)
                    goto CLEANUP;

                if(RegQueryValueEx(hClassKey,
                                   _T("DriverDesc"),
                                   NULL,
                                   &dwType,
                                   (PBYTE)lpConDesc,
                                   &dwDataSize) != ERROR_SUCCESS)
                {
                    lpConDesc = NULL;
                    goto CLEANUP;
                }
            }
            else
                lpConDesc = NULL;

            break;
        }
    }

CLEANUP:
    if (hBaseKey != NULL)
        RegCloseKey(hBaseKey);
    if (hClassKey != NULL)
        RegCloseKey(hClassKey);
    if (lpConDesc != NULL)
        HeapFree(ProcessHeap, 0, lpPath);
    if (lpConDesc != NULL)
        HeapFree(ProcessHeap, 0, lpKeyClass);

    return lpConDesc;
}


VOID ShowInfo(BOOL bAll)
{
    PIP_ADAPTER_INFO pAdapterInfo = NULL;
    PIP_ADAPTER_INFO pAdapter = NULL;
    ULONG adaptOutBufLen = 0;
    PFIXED_INFO pFixedInfo = NULL;
    ULONG netOutBufLen = 0;

    /* call GetAdaptersInfo to obtain the adapter info */
    if (GetAdaptersInfo(pAdapterInfo, &adaptOutBufLen) == ERROR_BUFFER_OVERFLOW)
    {
        pAdapterInfo = (IP_ADAPTER_INFO *)HeapAlloc(ProcessHeap, 0, adaptOutBufLen);
        if (pAdapterInfo == NULL)
            return;

        if (GetAdaptersInfo(pAdapterInfo, &adaptOutBufLen) != NO_ERROR)
        {
            DoFormatMessage(0);
            HeapFree(ProcessHeap, 0, pAdapterInfo);
            return;
        }
    }
    else
    {
        DoFormatMessage(0);
        return;
    }

    /* call GetNetworkParams to obtain the network info */
    if(GetNetworkParams(pFixedInfo, &netOutBufLen) == ERROR_BUFFER_OVERFLOW)
    {
        pFixedInfo = (FIXED_INFO *)HeapAlloc(ProcessHeap, 0, netOutBufLen);
        if (pFixedInfo == NULL)
            return;

        if (GetNetworkParams(pFixedInfo, &netOutBufLen) != NO_ERROR)
        {
            DoFormatMessage(0);
            HeapFree(ProcessHeap, 0, pFixedInfo);
            return;
        }
    }
    else
    {
        DoFormatMessage(0);
        return;
    }

    pAdapter = pAdapterInfo;

    _tprintf(_T("\nReactOS IP Configuration\n\n"));
    if (bAll)
    {
        _tprintf(_T("\tHost Name . . . . . . . . . . . . : %s\n"), pFixedInfo->HostName);
        _tprintf(_T("\tPrimary DNS Suffix. . . . . . . . : \n"));
        _tprintf(_T("\tNode Type . . . . . . . . . . . . : %s\n"), GetNodeTypeName(pFixedInfo->NodeType));
        if (pFixedInfo->EnableRouting)
            _tprintf(_T("\tIP Routing Enabled. . . . . . . . : Yes\n"));
        else
            _tprintf(_T("\tIP Routing Enabled. . . . . . . . : No\n"));
        if (pAdapter->HaveWins)
            _tprintf(_T("\tWINS Proxy enabled. . . . . . . . : Yes\n"));
        else
            _tprintf(_T("\tWINS Proxy enabled. . . . . . . . : No\n"));
        _tprintf(_T("\tDNS Suffix Search List. . . . . . : %s\n"), pFixedInfo->DomainName);
    }

    while (pAdapter)
    {
        LPTSTR IntType, myConType;

        IntType = GetInterfaceTypeName(pAdapter->Type);
        myConType = GetConnectionType(pAdapter->AdapterName);

        _tprintf(_T("\n%s %s: \n\n"), IntType , myConType);

        if (myConType != NULL) HeapFree(ProcessHeap, 0, myConType);

        /* check if the adapter is connected to the media */
        if (_tcscmp(pAdapter->IpAddressList.IpAddress.String, "0.0.0.0") == 0)
        {
            _tprintf(_T("\tMedia State . . . . . . . . . . . : Media disconnected\n"));
            pAdapter = pAdapter->Next;
            continue;
        }

        _tprintf(_T("\tConnection-specific DNS Suffix. . : %s\n"), pFixedInfo->DomainName);

        if (bAll)
        {
            _tprintf(_T("\tDescription . . . . . . . . . . . : %s\n"), GetConnectionDescription(pAdapter->AdapterName));
            _tprintf(_T("\tPhysical Address. . . . . . . . . : %s\n"), PrintMacAddr(pAdapter->Address));
            if (pAdapter->DhcpEnabled)
                _tprintf(_T("\tDHCP Enabled. . . . . . . . . . . : Yes\n"));
            else
                _tprintf(_T("\tDHCP Enabled. . . . . . . . . . . : No\n"));
            _tprintf(_T("\tAutoconfiguration Enabled . . . . : \n"));
        }

        _tprintf(_T("\tIP Address. . . . . . . . . . . . : %s\n"), pAdapter->IpAddressList.IpAddress.String);
        _tprintf(_T("\tSubnet Mask . . . . . . . . . . . : %s\n"), pAdapter->IpAddressList.IpMask.String);
        _tprintf(_T("\tDefault Gateway . . . . . . . . . : %s\n"), pAdapter->GatewayList.IpAddress.String);

        if (bAll)
        {
            PIP_ADDR_STRING pIPAddr;

            if (pAdapter->DhcpEnabled)
               _tprintf(_T("\tDHCP Server . . . . . . . . . . . : %s\n"), pAdapter->DhcpServer.IpAddress.String);

            _tprintf(_T("\tDNS Servers . . . . . . . . . . . : "));
            _tprintf(_T("%s\n"), pFixedInfo->DnsServerList.IpAddress.String);
            pIPAddr = pFixedInfo->DnsServerList.Next;
            while (pIPAddr)
            {
                _tprintf(_T("\t\t\t\t\t    %s\n"), pIPAddr ->IpAddress.String );
                pIPAddr = pIPAddr->Next;
            }

            if (pAdapter->HaveWins)
            {
                _tprintf(_T("\tPrimary WINS Server . . . . . . . : %s\n"), pAdapter->PrimaryWinsServer.IpAddress.String);
                _tprintf(_T("\tSecondard WINS Server . . . . . . : %s\n"), pAdapter->SecondaryWinsServer.IpAddress.String);
            }

            if (pAdapter->DhcpEnabled)
            {
                _tprintf(_T("\tLease Obtained. . . . . . . . . . : %s"), _tasctime(localtime(&pAdapter->LeaseObtained)));
                _tprintf(_T("\tLease Expires . . . . . . . . . . : %s"), _tasctime(localtime(&pAdapter->LeaseExpires)));
            }
        }
        _tprintf(_T("\n"));

        pAdapter = pAdapter->Next;

    }

    HeapFree(ProcessHeap, 0, pFixedInfo);
    HeapFree(ProcessHeap, 0, pAdapterInfo);
}

VOID Release(LPTSTR Index)
{
    IP_ADAPTER_INDEX_MAP AdapterInfo;
    DWORD ret;

    /* if interface is not given, query GetInterfaceInfo */
    if (Index == NULL)
    {
        PIP_INTERFACE_INFO pInfo = NULL;
        ULONG ulOutBufLen = 0;

        if (GetInterfaceInfo(pInfo, &ulOutBufLen) == ERROR_INSUFFICIENT_BUFFER)
        {
            pInfo = (IP_INTERFACE_INFO *)HeapAlloc(ProcessHeap, 0, ulOutBufLen);
            if (pInfo == NULL)
                return;

            if (GetInterfaceInfo(pInfo, &ulOutBufLen) == NO_ERROR )
            {
                CopyMemory(&AdapterInfo, &pInfo->Adapter[0], sizeof(IP_ADAPTER_INDEX_MAP));
                _tprintf(_T("name - %S\n"), pInfo->Adapter[0].Name);
                HeapFree(ProcessHeap, 0, pInfo);
            }
            else
            {
                DoFormatMessage(0);
                HeapFree(ProcessHeap, 0, pInfo);
                return;
            }
        }
        else
        {
            DoFormatMessage(0);
            return;
        }
    }
    else
    {
        ;
        /* FIXME:
         * we need to be able to release connections by name with support for globbing
         * i.e. ipconfig /release Eth* will release all cards starting with Eth...
         *      ipconfig /release *con* will release all cards with 'con' in their name
         */
    }


    /* Call IpReleaseAddress to release the IP address on the specified adapter. */
    if ((ret = IpReleaseAddress(&AdapterInfo)) != NO_ERROR)
    {
        _tprintf(_T("\nAn error occured while releasing interface %S : \n"), AdapterInfo.Name);
        DoFormatMessage(ret);
    }

}




VOID Renew(LPTSTR Index)
{
    IP_ADAPTER_INDEX_MAP AdapterInfo;

    /* if interface is not given, query GetInterfaceInfo */
    if (Index == NULL)
    {
        PIP_INTERFACE_INFO pInfo;
        ULONG ulOutBufLen = 0;

        pInfo = (IP_INTERFACE_INFO *)HeapAlloc(ProcessHeap, 0, sizeof(IP_INTERFACE_INFO));
        if (pInfo == NULL)
        {
            _tprintf(_T("memory allocation error"));
            return;
        }

        /* Make an initial call to GetInterfaceInfo to get
         * the necessary size into the ulOutBufLen variable */
        if ( GetInterfaceInfo(pInfo, &ulOutBufLen) == ERROR_INSUFFICIENT_BUFFER)
        {
            HeapFree(ProcessHeap, 0, pInfo);
            pInfo = (IP_INTERFACE_INFO *)HeapAlloc(ProcessHeap, 0, ulOutBufLen);
            if (pInfo == NULL)
            {
                _tprintf(_T("memory allocation error"));
                return;
            }
        }

        /* Make a second call to GetInterfaceInfo to get the actual data we want */
        if (GetInterfaceInfo(pInfo, &ulOutBufLen) == NO_ERROR )
        {
            CopyMemory(&AdapterInfo, &pInfo->Adapter[0], sizeof(IP_ADAPTER_INDEX_MAP));
            _tprintf(_T("name - %S\n"), pInfo->Adapter[0].Name);
        }
        else
        {
            _tprintf(_T("\nGetInterfaceInfo failed : "));
            DoFormatMessage(0);
        }

        HeapFree(ProcessHeap, 0, pInfo);
    }
    else
    {
        ;
        /* FIXME:
         * we need to be able to renew connections by name with support for globbing
         * i.e. ipconfig /renew Eth* will renew all cards starting with Eth...
         *      ipconfig /renew *con* will renew all cards with 'con' in their name
         */
    }


    /* Call IpRenewAddress to renew the IP address on the specified adapter. */
    if (IpRenewAddress(&AdapterInfo) != NO_ERROR)
    {
        _tprintf(_T("\nAn error occured while renew interface %s : "), _T("*name*"));
        DoFormatMessage(0);
    }
}



VOID Usage(VOID)
{
    HRSRC hRes;
    LPTSTR lpUsage;
    DWORD Size;

    LPTSTR lpName = (LPTSTR)MAKEINTRESOURCE((IDS_USAGE >> 4) + 1);

    hRes = FindResource(hInstance,
                        lpName,
                        RT_STRING);
    if (hRes != NULL)
    {
        if ((Size = SizeofResource(hInstance,
                                   hRes)))
        {
            lpUsage = (LPTSTR)HeapAlloc(ProcessHeap,
                                        0,
                                        Size);
            if (lpUsage == NULL)
                return;

            if (LoadString(hInstance,
                           IDS_USAGE,
                           lpUsage,
                           Size))
            {
                _tprintf(_T("%s"), lpUsage);
            }
        }
    }


}

int main(int argc, char *argv[])
{
    BOOL DoUsage=FALSE;
    BOOL DoAll=FALSE;
    BOOL DoRelease=FALSE;
    BOOL DoRenew=FALSE;
    BOOL DoFlushdns=FALSE;
    BOOL DoRegisterdns=FALSE;
    BOOL DoDisplaydns=FALSE;
    BOOL DoShowclassid=FALSE;
    BOOL DoSetclassid=FALSE;

    hInstance = GetModuleHandle(NULL);
    ProcessHeap = GetProcessHeap();

    /* Parse command line for options we have been given. */
    if ( (argc > 1)&&(argv[1][0]=='/') )
    {
        if( !_tcsicmp( &argv[1][1], _T("?") ))
        {
            DoUsage = TRUE;
        }
        else if( !_tcsnicmp( &argv[1][1], _T("ALL"), _tcslen(&argv[1][1]) ))
        {
           DoAll = TRUE;
        }
        else if( !_tcsnicmp( &argv[1][1], _T("RELEASE"), _tcslen(&argv[1][1]) ))
        {
            DoRelease = TRUE;
        }
        else if( ! _tcsnicmp( &argv[1][1], _T("RENEW"), _tcslen(&argv[1][1]) ))
        {
            DoRenew = TRUE;
        }
        else if( ! _tcsnicmp( &argv[1][1], _T("FLUSHDNS"), _tcslen(&argv[1][1]) ))
        {
            DoFlushdns = TRUE;
        }
        else if( ! _tcsnicmp( &argv[1][1], _T("FLUSHREGISTERDNS"), _tcslen(&argv[1][1]) ))
        {
            DoRegisterdns = TRUE;
        }
        else if( ! _tcsnicmp( &argv[1][1], _T("DISPLAYDNS"), _tcslen(&argv[1][1]) ))
        {
            DoDisplaydns = TRUE;
        }
        else if( ! _tcsnicmp( &argv[1][1], _T("SHOWCLASSID"), _tcslen(&argv[1][1]) ))
        {
            DoShowclassid = TRUE;
        }
        else if( ! _tcsnicmp( &argv[1][1], _T("SETCLASSID"), _tcslen(&argv[1][1]) ))
        {
            DoSetclassid = TRUE;
        }
    }

    switch (argc)
    {
        case 1:  /* Default behaviour if no options are given*/
            ShowInfo(FALSE);
            break;
        case 2:  /* Process all the options that take no parameters */
            if (DoUsage)
                Usage();
            else if (DoAll)
                ShowInfo(TRUE);
            else if (DoRelease)
                Release(NULL);
            else if (DoRenew)
                Renew(NULL);
            else if (DoFlushdns)
                _tprintf(_T("\nSorry /flushdns is not implemented yet\n"));
            else if (DoRegisterdns)
                _tprintf(_T("\nSorry /registerdns is not implemented yet\n"));
            else if (DoDisplaydns)
                _tprintf(_T("\nSorry /displaydns is not implemented yet\n"));
            else
                Usage();
            break;
        case 3: /* Process all the options that can have 1 parameter */
            if (DoRelease)
                _tprintf(_T("\nSorry /release [adapter] is not implemented yet\n"));
                //Release(argv[2]);
            else if (DoRenew)
                _tprintf(_T("\nSorry /renew [adapter] is not implemented yet\n"));
            else if (DoShowclassid)
                _tprintf(_T("\nSorry /showclassid adapter is not implemented yet\n"));
            else if (DoSetclassid)
                _tprintf(_T("\nSorry /setclassid adapter is not implemented yet\n"));
            else
                Usage();
            break;
        case 4:  /* Process all the options that can have 2 parameters */
            if (DoSetclassid)
                _tprintf(_T("\nSorry /setclassid adapter [classid]is not implemented yet\n"));
            else
                Usage();
            break;
        default:
            Usage();
    }

    return 0;
}

