// $Id: win32_adapter.c,v 1.29 2012/11/19 19:32:19 lpoulsen Exp $
//
// Copyright (c) 2012 AFAR Communications Inc, Santa Barbara, CA 93117 USA
// Copyright (C) 2007 AFAR Communications Inc, Santa Barbara, CA 93117 USA
// Copyright (C) 2001 Wi_LAN Technologies, Inc
//
// Econsole ADAPTER module for Windows 2000 and Windows XP
//  using newer WS2_32 and IPhelper APIs.
// 
// For older Windows versions (95/98/NT) use ECON98 program
//  which uses the other windows_adapter.c file

// #define WIN2K

// Build note:
// ----------
//  January 2012: Switched to using the Windows v7.0 SDK
//    This introduced some compatibility issues when using the
//    VS7 VC++ (Visual Studio 2003 .NET). Since we want to be
//    able to use VS7 or VS 10.0 (Express) at will, we needed to
//    address these. For now I have done that by copying the following
//    from VS10/VC/include to SDK/Windows/v7.0/Include:
//       sal.h
//       CodeAnalysis/ directory (just 2 files)
//    We had another problem which was caused by no less than 2 local
//    copies of NTddNdis.h; I removed both of these, so we got the
//    more modern version we needed from the Windows/v7.0 SDK.
//
// $Log: win32_adapter.c,v $
// Revision 1.29  2012/11/19 19:32:19  lpoulsen
// Print real pcap error message when you get ethernet send errors, instead
//   of just a generic message "Error sending packet".
//
// Revision 1.28  2012/10/30 19:52:27  lpoulsen
// Add compile-time option for WIN2K (omit newer system calls)
//
// Revision 1.27  2012/05/03 19:15:16  lpoulsen
// Correct minor compiler warnings and header file dependencies.
//
// Revision 1.26  2012/01/13 00:55:06  lpoulsen
// Update to current platform SDK
// Print WinPcap version and OS 32/64 bit type from version command
//
// Revision 1.25  2011/10/07 00:52:04  lpoulsen
// Get rid of compile warnings with a cast or two.
//
// Revision 1.24  2011/08/25 21:58:40  lpoulsen
// Add IP address info to jumbo pkt diagnostic
//
// Revision 1.23  2011/08/23 18:42:44  lpoulsen
// Suppress message for bad packet length unless "debug nethealth" is set.
//   With "debug nethealth" dump first 46 bytes of packet./
//
// Revision 1.22  2011/07/08 21:50:30  lpoulsen
// Eliminate (almost all) use of debug_level, use debug_flags instead.
//
// Revision 1.21  2011/03/22 00:26:10  lpoulsen
// Resolve sequence errors on very weak links
// Resolve hangs when Ctrl-C between "dir" and "download"
// Resolve conflicting build date information
//
// Revision 1.20  2011/03/03 01:44:32  lpoulsen
// If only one adapter, connect to it.
//
// Revision 1.19  2011/02/18 22:31:09  lpoulsen
// Add "adapter" command to switch to another ethernet port
//
// Revision 1.18  2011/02/08 01:19:54  lpoulsen
// Abandoned econ98 (windows_adapter.c)
//
// Revision 1.17  2011/02/04 22:29:07  lpoulsen
// Many little fixes.
//
// Revision 1.16  2011/01/22 01:29:05  lpoulsen
// Release candidate:
// - allow "show first=1 max=45" to support large systems
// - allow "sort by=distance" to support "connect all reboot"
// - improve download efficiency
//
// Revision 1.15  2010/12/22 17:36:34  lpoulsen
// Select/Deselect list of ranges
// Expand to up to 511 radios
// Protocol fixes
//
// Revision 1.14  2009/06/16 17:19:41  lpoulsen
// Allow WiFi as ethernet type
//
// Revision 1.13  2009/06/15 23:44:41  lpoulsen
// Correctly retrieve IPv4 address on a system with IPv6 enabled
//
// Revision 1.12  2009/06/15 22:08:08  lpoulsen
// Add fixes to work on Vista64
//
// Revision 1.11  2008/08/05 23:58:37  lpoulsen
// User interface updates.
//
// Revision 1.10  2008/07/11 20:32:02  lpoulsen
// Resolve Win/Lx discrepancy
//
// Revision 1.9  2008/05/31 00:35:09  lpoulsen
// Econ 4.x preview - now with command shell
//
// Revision 1.8  2008/04/26 00:48:25  lpoulsen
// Rename selectedAdapterId global var to option_C
//
// Revision 1.7  2007/05/15 18:53:00  lpoulsen
// ECON98 version 03.05
//     Now correctly enumerates the devices on Windows98 first edition.
//
// Revision 1.6  2006/10/05 17:45:22  lpoulsen
// Increase buffer size for XP GetAdapterInfo call
//
// Revision 1.5  2006/04/27 19:27:14  lpoulsen
// Allow for pathnames longer than 24 bytes.
//
// Revision 1.4  2006/02/21 01:04:09  lpoulsen
// Now compiles on linux as well as on Windows with Visual Studio 2005 Express
//
// Revision 1.3  2005/06/24 18:42:57  lpoulsen
// Rename the PCAP buffer size and make sure it is validly defined.
//
// Revision 1.2  2005/05/12 18:02:08  lpoulsen
// Fix interface filtering. Now works on Win98SE, Win2000, WinXP
//
// Revision 1.1  2005/05/11 17:04:33  lpoulsen
// Solve problems on WinXP by moving to WinPCap 3.1 beta 4
// Use IP helper API to get MAC addresses (no longer works on Win95)
// Change references to "radios" to say "devices"
//
// Revision 1.15  2003/08/25 20:43:32  lpoulsen
// Correctly handle queue overflow on busy network.
//
// ----
// Fresh port from linux_adapter.h
//
#ifndef WIN32
#define WIN32
#endif
#define PCAP_DONT_INCLUDE_PCAP_BPF_H
#define WINVER 0x0501       // Assume at least WinXP

#include <stdio.h>
#include <stdlib.h>
// #include "pcap_w31b4.h"  // Ported in from WinPcap3_1b4/wpcap/libpcap/pcap.h
#include "pcapafar.h"
#include <errno.h>

#include "windows.h"

// #include "libnet.h" /* libnet - you can also use <libnet.h> */
// #include <sys/socket.h>
// #include <netinet/in.h>
// #include <arpa/inet.h>
// #include <netinet/if_ether.h> /* includes net/ethernet.h */
// #include <unistd.h>

#include <time.h>
#include "afartype.h"
#include "epacket.h"
#include "adapter.h"
#include "util.h"
#include "ucon.h"               // option_C
#include "windows_threads.h"
#include "Iphlpapi.h"


#define PCAP_BUFSIZE     1520
#define MAX_IPALIST_SIZE 8192

BOOL isWinNT = FALSE;

typedef u_char ip_addr[4];

struct adapter_struct ethernet_adapters[Max_Num_Adapter+1];

char *format_sa_inet(void *vp);
char *format_mac_addr(u_char *map);
char *format_ip_addr(ip_addr *ipa);

#define MAX_LOCAL_HOST_NAME 32

pcap_t *descr;
char   *dev;                      /* name of the device to use */
char    devaddr[48];
struct  libnet_link_int *link_interface;
char    localHostName[MAX_LOCAL_HOST_NAME];
ipAddr  my_ip_address  = { 0,0,0,0 };
macAddr my_mac_address = { 0,0,0,0,0,0 };

int     num_of_ether_adapters;

int
get_adapters()
{
    find_adapters(true);
    return open_adapter(option_C);
}
int
find_adapters(boolean show)
{
    char             errbuf[PCAP_ERRBUF_SIZE];
    int              i, usable_ports, chosen_port;
    pcap_if_t       *alldevs;
    pcap_if_t       *d;

    OSVERSIONINFO    osinfo;
    char            *osName = "Unknown (99999)";

    DWORD            status;

    IP_ADAPTER_INFO *ipalist, *ipa;


    errbuf[0] = 0;

    // first get host name

    i = MAX_LOCAL_HOST_NAME;
    GetComputerName(localHostName, &i);

    osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if (!GetVersionEx(&osinfo))
    {
        printf("GetVersionEx failed\n");
        exit(1);
    }

    if (osinfo.dwPlatformId == 1)
        osName = "Win9x";
    else if (osinfo.dwPlatformId == 2)
    {
        if (osinfo.dwMajorVersion <= 4)
            osName = "WinNT";
        else {
        if (     (osinfo.dwMajorVersion == 5) && osinfo.dwMinorVersion == 0)
            osName = "Win2K";
        else if ((osinfo.dwMajorVersion == 5) && osinfo.dwMinorVersion == 1)
            osName = "WinXP";
        else if ((osinfo.dwMajorVersion == 5) && osinfo.dwMinorVersion == 2)
            osName = "WinXP 64-bit";
        else if ((osinfo.dwMajorVersion == 6) && osinfo.dwMinorVersion == 0)
            osName = "Vista";
        else if ((osinfo.dwMajorVersion == 6) && osinfo.dwMinorVersion == 1)
            osName = "Windows7";
        else if ((osinfo.dwMajorVersion == 6) && osinfo.dwMinorVersion == 2)
            osName = "Windows8";
        else
            osName = "Win2K/WinXP/Vista/??";
        }
    }
    else if (osinfo.dwPlatformId == 3)
        osName = "WinCE";
    else
        sprintf(osName, "Unknown (%d)", osinfo.dwPlatformId & 0xFFFF);

    printf("Host name:  %s  - OS Version: %s %d.%d (%d) %s\n",
               localHostName,                 osName,
               osinfo.dwMajorVersion,         osinfo.dwMinorVersion,
               osinfo.dwBuildNumber & 0xFFFF, osinfo.szCSDVersion);
    if (debug_flags & DBFLG_ADAPTER)
        printf("WinPCap Version: %-53.53s\n", pcap_lib_version());

    if (osinfo.dwPlatformId == 2)
        isWinNT = TRUE;

    memset(&ethernet_adapters, 0, sizeof(ethernet_adapters));

    // ask pcap for a list of all devices
    if (pcap_findalldevs_ex("*", NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr, "Error in pcap_findalldevs_ex: %s\n", errbuf);
        fprintf(stderr,
                "*** Are you sure you have a current WinPCap installed? ***\n");
        exit(1);
    }
    for (d = alldevs, i = 0; d; d = d->next)
    {
        struct pcap_addr *pca;
        int               pcai;

        strncpy(ethernet_adapters[i].name, d->name, 80);
        ethernet_adapters[i].name[79] = 0;
        strncpy(ethernet_adapters[i].desc, d->description, 80);
        ethernet_adapters[i].desc[79] = 0;
        ethernet_adapters[i].flags |= EA_VALID;

        if (debug_flags & DBFLG_ADAPTER) {
            printf("%d: %s\n   %-76.76s\n", i, d->name, d->description);
        }
        pcai = 0;
        pca = d->addresses;
        while (pca) {
            // look at this address entry;
            struct sockaddr *sa = pca->addr;
            if (debug_flags & DBFLG_ADAPTER)
                printf("   addr %d: %08X family: %d (%02x %02x %02x %02x %02x %02x)\n",
                            pcai, sa, sa->sa_family,
                            sa->sa_data[0] & 0xFF,
                            sa->sa_data[1] & 0xFF,
                            sa->sa_data[2] & 0xFF,
                            sa->sa_data[3] & 0xFF,
                            sa->sa_data[4] & 0xFF,
                            sa->sa_data[5] & 0xFF);
            if (sa->sa_family == 2) {
                memcpy(&ethernet_adapters[i].ip_addr, &sa->sa_data[2], 4);
                ethernet_adapters[i].flags |= EA_IP_ADDR;
                if (debug_flags & DBFLG_ADAPTER)
                    printf("           IP=%s\n",
                           format_ip_addr(&ethernet_adapters[i].ip_addr));
            }
            pca = pca->next;
            pcai++;
        }
        i++;
    }
    pcap_freealldevs(alldevs);
    alldevs = d = NULL;
    num_of_ether_adapters = i;

    ipalist = malloc(MAX_IPALIST_SIZE);
    i = MAX_IPALIST_SIZE;
    status = GetAdaptersInfo(ipalist, &i);
    if (status != ERROR_SUCCESS)
    {
        printf("GetAdaptersInfo() failed, error %d\n", status);
        if (status == ERROR_BUFFER_OVERFLOW)
            printf("Buffer of %d bytes is too short, %d required\n",
                   MAX_IPALIST_SIZE, i);
        if (status == ERROR_NOT_SUPPORTED)
            printf("Unsupported OS version\n");
        exit(1);
    }
    for (ipa = ipalist; ipa; ipa = ipa->Next)
    {
        if (debug_flags & DBFLG_ADAPTER) {
            printf("*** %s\n", &ipa->AdapterName);
            printf("    %-75.75s\n", &ipa->Description);
            printf("    IP=%s MAC=%s (%d) Type=%d\n",
                    &ipa->IpAddressList.IpAddress,
                    format_mac_addr((void *)&ipa->Address),
                    ipa->AddressLength, ipa->Type);
        }
        // if the address length is not 6, it's not ethernet for sure
        if (ipa->AddressLength != 6)
            continue;
        if ((ipa->Type != MIB_IF_TYPE_ETHERNET)
         && (ipa->Type != IF_TYPE_IEEE80211)   )
            continue;
        for (i = 0; i < num_of_ether_adapters; i++)
        {
            if (strstr(ethernet_adapters[i].name, "PPPMAC") != NULL)
            {
                // Unbelievably, in Win98, the PPP device is type Ethernet!!
                continue;
            }
            if (strlen(ipa->AdapterName))
            {   // NT family, devices have sane (but ugly) names
                if (strstr(ethernet_adapters[i].name, ipa->AdapterName) != NULL)
                {
                    memcpy(ethernet_adapters[i].mac_addr, &ipa->Address, 6);
                    ethernet_adapters[i].flags |= (EA_MAC_ADDR | EA_USABLE);
                    if (debug_flags & DBFLG_ADAPTER)
                        printf("    Matched to slot %d\n", i);

                    if ((*(uint32*)&ipa->IpAddressList.IpAddress)
                     && (0 == *(uint32*)&ethernet_adapters[i].ip_addr))
                    {
                        if (debug_flags & DBFLG_ADAPTER)
                            printf("    Moved the IP address \n");

                        *(uint32 *)&ethernet_adapters[i].ip_addr =
                            inet_addr((void *)&ipa->IpAddressList.IpAddress);
                    }
                    break;
                }
            } else
            {   // Win98, no names but descriptors
                if (strstr(ethernet_adapters[i].desc, ipa->Description) != NULL)
                {
                    memcpy(ethernet_adapters[i].mac_addr, &ipa->Address, 6);
                    ethernet_adapters[i].flags |= (EA_MAC_ADDR | EA_USABLE);
                    if (debug_flags & DBFLG_ADAPTER)
                        printf("    Matched to slot %d\n", i);
                    break;
                }
            }
        }
    }
    free(ipalist);
    ipalist = NULL;

    usable_ports = 0;
    for (i = 0; i < num_of_ether_adapters; i++)
    {
        if (ethernet_adapters[i].flags & EA_USABLE)
        {
            usable_ports++;
            chosen_port = i;
        }
    }
    if (show 
     && ((usable_ports > 1) || (debug_flags & DBFLG_ADAPTER)) )
    {
       printf("You have %d network connections:\n", usable_ports);
       for (i = 0; i < num_of_ether_adapters; i++)
       {
           if (ethernet_adapters[i].flags & EA_USABLE)
           {
               printf("%d %-18s %-15s %-40.40s\n",
                     i, format_mac_addr((void *)&ethernet_adapters[i].mac_addr),
                      format_ip_addr(&ethernet_adapters[i].ip_addr),
                      ethernet_adapters[i].desc);
           }
       }
    }
    if ((option_C == -1) && (usable_ports == 1))
        option_C = chosen_port;

    if (debug_flags & DBFLG_ADAPTER)
        printf("num_of_ether_adapters=%d option_C=%d\n",
               num_of_ether_adapters, option_C);

    return num_of_ether_adapters;
}

int
open_adapter(int chosen_port)
{
    char  adapnum[12];
    char  errbuf[PCAP_ERRBUF_SIZE];
    int   promisc = 0;            // false
    int   to_ms   = 5;            // timeout in millisecs

    while ( (chosen_port < 0)
         || (chosen_port > num_of_ether_adapters-1)
         || !(ethernet_adapters[chosen_port].flags & EA_USABLE) )
    {
        printf("Select adapter #:");
        fgets(adapnum, 10, stdin);
        if (strlen(adapnum) < 1)
            continue;
        if (adapnum[0] < '0')
            continue;
        chosen_port = atoi(adapnum);
        if ( (chosen_port < 0)
          || (chosen_port > num_of_ether_adapters-1)
          || !(ethernet_adapters[chosen_port].flags & EA_USABLE) )
        {
                    printf("Invalid adapter number\n");
                    chosen_port = -1;
                    continue;
        }
    }
    dev = ethernet_adapters[chosen_port].name;
    memcpy(my_mac_address, ethernet_adapters[chosen_port].mac_addr, 6);
    memcpy(my_ip_address,  ethernet_adapters[chosen_port].ip_addr, 4);

    /* error checking */
    if (dev == NULL)
    {
        printf("%s\n", errbuf);
        exit(1);
    }

    descr = pcap_open(dev, PCAP_BUFSIZE, promisc, to_ms, NULL, errbuf);
    if (descr == NULL)
    {
        printf("pcap_open_live(%s): %s\n", dev, errbuf);
        exit(-1);
    }
    return chosen_port;
}

int
close_all_adapters()
{
    if (descr != NULL)
        pcap_close(descr);
    return 0;                   //everything OK

}
// -- adapter_receive(packet)
//    packet is a pointer to a memory buffer, into which the incoming
//           packet will be copied.
//    returns  length of packet copied
//             0      if no packet available (timeout!)
//
int
adapter_receive(unsigned char *packet)
{
    int status;
    struct pcap_pkthdr *pkt_header;
    u_char             *pkt_data;

    status = pcap_next_ex(descr, &pkt_header, &pkt_data);
    switch (status)
    {
    case -1: // Error
    case -2: // Eof
        printf("adapter_receive failed\n");
        exit(1);
    case 0: // Timeout
        return 0;
    case 1: // Normal - fall through
        ;
    }
    if ((pkt_header->len > 1518)
     || (pkt_header->len != pkt_header->caplen))
    {
        int i;

        if (debug_flags & DBFLG_NETHEALTH) {
            printf("Bad packet length %d/%d\n",
                   pkt_header->len, pkt_header->caplen);
            printf("    Packet header begins:\n        ");
            for (i = 0; i < 14; i++) {
                printf("%02X ", pkt_data[i]);
                if ((i == 5) || (i == 11))
                    printf("  ");
            }
            printf("\n");
            for (i = 14; i < 46; i++) {
                if (((i - 14) % 16) == 0)
                    printf("        ");
                printf("%02X ", pkt_data[i]);
                if (((i - 14) % 16) == 15)
                    printf("\n");
                else if (((i - 14) % 8) == 7)
                    printf("  ");
            }
            if ((pkt_data[12] = 0x08) && (pkt_data[13] == 0)) {
                printf("    IP from %s to %s\n",
                       format_ip_addr((void*)&pkt_data[14+12]),
                       format_ip_addr((void*)&pkt_data[14+16]));
            }
        }
        return 0;
    }
    memcpy(packet, pkt_data, pkt_header->len);
    return pkt_header->len;
}

int
adapter_send(u_char * packet_to_send, int packet_size)
{
    int status;

    status = pcap_sendpacket(descr, packet_to_send, packet_size);
    if (status == 0)
        return packet_size;

    pcap_perror(descr, "adapter_send() - Error sending packet: ");
    return 0;
}

char *
format_sa_inet(void *vp)
{
    struct sockaddr_in *sap = vp;
    static int ii = 0;
    static char ipastr[4][16];

    if (sap->sin_family != AF_INET)
    {
        printf("Address format: %d (%04X)\n", sap->sin_family, sap->sin_family);
        return "Not AF_INET";
    }
    ii = ++ii & 0x03;
    sprintf(&ipastr[ii][0], "%d.%d.%d.%d",
            sap->sin_addr.S_un.S_un_b.s_b1,
            sap->sin_addr.S_un.S_un_b.s_b2,
            sap->sin_addr.S_un.S_un_b.s_b3,
            sap->sin_addr.S_un.S_un_b.s_b4);
    return &ipastr[ii][0];
}

void
print_system_version(void)
{
    OSVERSIONINFO    osinfo;
    char            *osName = "Unknown (99999)";
    char            *architecture = "Unknown";
#ifndef WIN2K
    SYSTEM_INFO      sys_info;
    BOOL             isWow  = false;
#endif

    osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if (!GetVersionEx(&osinfo))
    {
        printf("GetVersionEx failed\n");
        exit(1);
    }

    if (osinfo.dwPlatformId == 1)
        osName = "Win9x";
    else if (osinfo.dwPlatformId == 2)
    {
        if (osinfo.dwMajorVersion <= 4)
            osName = "WinNT";
        else {
        if (     (osinfo.dwMajorVersion == 5) && osinfo.dwMinorVersion == 0)
            osName = "Win2K";
        else if ((osinfo.dwMajorVersion == 5) && osinfo.dwMinorVersion == 1)
            osName = "WinXP";
        else if ((osinfo.dwMajorVersion == 5) && osinfo.dwMinorVersion == 2)
            osName = "WinXP 64-bit";
        else if ((osinfo.dwMajorVersion == 6) && osinfo.dwMinorVersion == 0)
            osName = "Vista";
        else if ((osinfo.dwMajorVersion == 6) && osinfo.dwMinorVersion == 1)
            osName = "Windows7";
        else if ((osinfo.dwMajorVersion == 6) && osinfo.dwMinorVersion == 2)
            osName = "Windows8";
        else
            osName = "Win2K/WinXP/Vista/??";
        }
        isWinNT = TRUE;
    }
    else if (osinfo.dwPlatformId == 3)
        osName = "WinCE";
    else
        sprintf(osName, "Unknown (%d)", osinfo.dwPlatformId & 0xFFFF);

    printf("Host name:  %s  - OS Version: %s %d.%d (%d) %s\n",
               localHostName,                 osName,
               osinfo.dwMajorVersion,         osinfo.dwMinorVersion,
               osinfo.dwBuildNumber & 0xFFFF, osinfo.szCSDVersion);

#ifndef WIN2K
    GetNativeSystemInfo(&sys_info);

    if (IsWow64Process(0, &isWow)) {
        printf("[IsWow64Process() failed - errno=%d]\n", GetLastError());
        isWow = false;
    }
    if (sys_info.wProcessorArchitecture == 0)
        architecture = "32-bit (x86)";
    else if (sys_info.wProcessorArchitecture == 6)
        architecture = "64-bit (Itanium)";
    else if (sys_info.wProcessorArchitecture == 9)
        architecture = "64-bit (x64 AMD or Intel)";

    printf("CPU type:   %s %s\n",
           architecture, (isWow ? "with Wow64" : "native"));
#endif

    printf("WinPCap Version: %-53.53s\n", pcap_lib_version());

    return;
}
