/***********************************************************************/
/*  TABMAC: Author Olivier Bertrand -- PlugDB -- 2008-2012             */
/*  From the article and sample code by Khalid Shaikh.                 */
/*  TABMAC: virtual table to get the list of MAC addresses.            */
/***********************************************************************/
#if defined(_WIN32)
#include "my_global.h"
//#include <iphlpapi.h>
#else   // !_WIN32
#error This is a WINDOWS only table type
#endif  // !_WIN32
#include "global.h"
#include "plgdbsem.h"
//#include "catalog.h"
//#include "reldef.h"
#include "xtable.h"
#include "colblk.h"
#include "tabmac.h"

#if 0    // This is placed here just to know what are the actual values
#define MAX_ADAPTER_DESCRIPTION_LENGTH  128
#define MAX_ADAPTER_NAME_LENGTH         256
#define MAX_ADAPTER_ADDRESS_LENGTH      8  
#define DEFAULT_MINIMUM_ENTITIES        32 
#define MAX_HOSTNAME_LEN                128
#define MAX_DOMAIN_NAME_LEN             128
#define MAX_SCOPE_ID_LEN                256

#define BROADCAST_NODETYPE              1
#define PEER_TO_PEER_NODETYPE           2
#define MIXED_NODETYPE                  4
#define HYBRID_NODETYPE                 8

#define IP_ADAPTER_DDNS_ENABLED               0x01
#define IP_ADAPTER_REGISTER_ADAPTER_SUFFIX    0x02
#define IP_ADAPTER_DHCP_ENABLED               0x04
#define IP_ADAPTER_RECEIVE_ONLY               0x08
#define IP_ADAPTER_NO_MULTICAST               0x10
#define IP_ADAPTER_IPV6_OTHER_STATEFUL_CONFIG 0x20
#endif // 0

/* -------------- Implementation of the MAC classes  ------------------ */

/***********************************************************************/
/*  DefineAM: define specific AM block values from MAC file.           */
/***********************************************************************/
bool MACDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
  {
  return false;
  } // end of DefineAM

/***********************************************************************/
/*  GetTable: makes a new TDB of the proper type.                      */
/***********************************************************************/
PTDB MACDEF::GetTable(PGLOBAL g, MODE m)
  {
  return new(g) TDBMAC(this);
  } // end of GetTable

/* ------------------------------------------------------------------- */

/***********************************************************************/
/*  Implementation of the TDBMAC class.                               */
/***********************************************************************/
TDBMAC::TDBMAC(PMACDEF tdp) : TDBASE(tdp)
  {
  FixedInfo = NULL;
  Piaf = NULL;
  Curp = NULL;
  Next = NULL;
  Buflen = 0;
  Fix = false;
  Adap = false;
  N = 0;
  } // end of TDBMAC constructor

/***********************************************************************/
/*  Allocate MAC column description block.                             */
/***********************************************************************/
PCOL TDBMAC::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
  {
  PCOL colp;

  colp = new(g) MACCOL(cdp, this, n);

  if (cprec) {
    colp->SetNext(cprec->GetNext());
    cprec->SetNext(colp);
  } else {
    colp->SetNext(Columns);
    Columns = colp;
  } // endif cprec

  return colp;
  } // end of MakeCol

/***********************************************************************/
/*  MAC: Get the number of found adapters.                             */
/***********************************************************************/
void TDBMAC::MakeErrorMsg(PGLOBAL g, DWORD drc)
  {
  if (drc == ERROR_BUFFER_OVERFLOW)
    snprintf(g->Message, sizeof(g->Message),
      "GetAdaptersInfo: Buffer Overflow buflen=%d maxsize=%d",
      Buflen, MaxSize);
  else if (drc == ERROR_INVALID_PARAMETER)
    strcpy(g->Message, "GetAdaptersInfo: Invalid parameters");
  else if (drc == ERROR_NO_DATA)
    strcpy(g->Message,
           "No adapter information exists for the local computer");
  else if (drc == ERROR_NOT_SUPPORTED)
    strcpy(g->Message, "GetAdaptersInfo is not supported");
  else
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                  FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(),
                  0, g->Message, sizeof(g->Message), NULL);

  } // end of MakeErrorMsg

/***********************************************************************/
/*  GetMacInfo: Get info for all found adapters.                       */
/***********************************************************************/
bool TDBMAC::GetMacInfo(PGLOBAL g)
  {
  DWORD drc;

  if (GetMaxSize(g) < 0)
    return true;
  else if (MaxSize == 0)
    return false;

  Piaf = (PIP_ADAPTER_INFO)PlugSubAlloc(g, NULL, Buflen); 
  drc = GetAdaptersInfo(Piaf, &Buflen);

  if (drc == ERROR_SUCCESS) {
    Next = Piaf;               // Next is the first one
    return false;               // Success
    } // endif drc

  MakeErrorMsg(g, drc);
  return true;
  } // end of GetMacInfo

/***********************************************************************/
/*  GetFixedInfo: Get info for network parameters.                     */
/***********************************************************************/
bool TDBMAC::GetFixedInfo(PGLOBAL g)
  {
  ULONG len;
  DWORD drc;

  FixedInfo = (FIXED_INFO*)PlugSubAlloc(g, NULL, sizeof(FIXED_INFO));
  len = sizeof(FIXED_INFO);
  drc = GetNetworkParams(FixedInfo, &len);
  
  if (drc == ERROR_BUFFER_OVERFLOW) {
    FixedInfo = (FIXED_INFO*)PlugSubAlloc(g, NULL, len);
    drc = GetNetworkParams(FixedInfo, &len);
    } // endif drc

  if (drc != ERROR_SUCCESS) {
    snprintf(g->Message, sizeof(g->Message),  "GetNetworkParams failed. Rc=%08x\n", drc);
    return true;
    } // endif drc

  return false;
  } // end of GetFixedInfo

/***********************************************************************/
/*  MAC: Get the number of found adapters.                             */
/***********************************************************************/
int TDBMAC::GetMaxSize(PGLOBAL g)
  {
  if (Use != USE_OPEN)
    // Called from info, Adap and Fix are not set yet
    return 1;

  if (MaxSize < 0) {
    // Best method
    if (Adap) {
      DWORD drc = GetAdaptersInfo(NULL, &(Buflen = 0));

      if (drc == ERROR_SUCCESS)
        MaxSize = (Fix) ? 1 : 0;
      else if (drc == ERROR_BUFFER_OVERFLOW) {
        // sizeof(IP_ADAPTER_INFO) was returning 640 but is now sometimes
        // returning 648 while the Buflen setting remains the same (n*640)
        // >> Of course, the code above contains a race condition.... 
        // if the size of the structure Windows wants to return grows after
        // the first call to GetAdaptersInfo() but before the second call 
        // to GetAdaptersInfo(), the second call to GetAdaptersInfo() will 
        // fail with ERROR_BUFFER_OVERFLOW as well, and your function won't 
        // work (by Jeremy Friesner on stackoverflow.com).
        // That's why we add something to it to be comfortable.
        MaxSize = (Buflen + 600) / sizeof(IP_ADAPTER_INFO);

        // Now Buflen must be updated if 648 is true.
        Buflen = MaxSize * sizeof(IP_ADAPTER_INFO);
      } else
        MakeErrorMsg(g, drc);

    } else
      MaxSize = (Fix) ? 1 : 0;

#if 0
    // This method returns too many adapters
    DWORD dw, drc = GetNumberOfInterfaces((PDWORD)&dw);

    if (drc == NO_ERROR) {
      MaxSize = (int)dw;
      Buflen = MaxSize * sizeof(IP_ADAPTER_INFO);
    } else
      MakeErrorMsg(g, 0);
#endif
    } // endif MaxSize

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  MAC Access Method opening routine.                                 */
/***********************************************************************/
bool TDBMAC::OpenDB(PGLOBAL g)
  {
  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open, this should not happen.                    */
    /*******************************************************************/
    strcpy(g->Message, "TDBMAC should not be reopened");
    return true;
    } // endif use

  if (Mode != MODE_READ) {
    /*******************************************************************/
    /* MAC tables cannot be modified.                                  */
    /*******************************************************************/
    strcpy(g->Message, "MAC tables are read only");
    return true;
  } else
    Use = USE_OPEN;

  /*********************************************************************/
  /*  Get the adapters info.                                           */
  /*********************************************************************/
  if (Adap && GetMacInfo(g))
    return true;

  if (Fix && GetFixedInfo(g))
    return true;

  /*********************************************************************/
  /*  All is done.                                                     */
  /*********************************************************************/
  return false;
  } // end of OpenDB

/***********************************************************************/
/*  Data Base read routine for MAC access method.                      */
/***********************************************************************/
int TDBMAC::ReadDB(PGLOBAL g)
  {
  Curp = Next;

  if (Curp)
    Next = Curp->Next;
  else if (N || !Fix)
    return RC_EF;
  
  N++;
  return RC_OK;
  } // end of ReadDB

/***********************************************************************/
/*  WriteDB: Data Base write routine for MAC access methods.           */
/***********************************************************************/
int TDBMAC::WriteDB(PGLOBAL g)
  {
  strcpy(g->Message, "MAC tables are read only");
  return RC_FX;
  } // end of WriteDB

/***********************************************************************/
/*  Data Base delete line routine for MAC access methods.              */
/***********************************************************************/
int TDBMAC::DeleteDB(PGLOBAL g, int irc)
  {
  strcpy(g->Message, "Delete not enabled for MAC tables");
  return RC_FX;
  } // end of DeleteDB

// ------------------------ MACCOL functions ----------------------------

/***********************************************************************/
/*  MACCOL public constructor.                                         */
/***********************************************************************/
MACCOL::MACCOL(PCOLDEF cdp, PTDB tdbp, int n)
      : COLBLK(cdp, tdbp, n)
  {
  Tdbp = (PTDBMAC)tdbp;
  Flag = cdp->GetOffset();

  if (Flag < 10)
    Tdbp->Fix = true;
  else
    Tdbp->Adap = true;

  } // end of MACCOL constructor

/***********************************************************************/
/*  Read the next MAC address elements.                                */
/***********************************************************************/
void MACCOL::ReadColumn(PGLOBAL g)
  {
  // Type conversion is handled by Value set routines
  char            *p = NULL, buf[260] = "";
  unsigned int     i;
  int             n = 0;
  PIP_ADAPTER_INFO adp = Tdbp->Curp;
  FIXED_INFO      *fip = Tdbp->FixedInfo;

  if (!adp && Flag >= 10) {
    // Fix info row, no adapter info available
    switch (Flag) {
      case 13:
      case 14:
      case 19:
      case 22:
      case 23:
        n = 0;
        break;
      default:
        p = PlugDup(g, "");
      } // endswitch Flag

  } else switch (Flag) {
    // FIXED INFO
    case 1:                      // Host Name
      p = fip->HostName;
      break;
    case 2:                      // Domain Name
      p = fip->DomainName;
      break;
    case 3:                      // DNS IPaddress
      p = (fip->CurrentDnsServer)
        ? (char*)&fip->CurrentDnsServer->IpAddress
        : (char*)&fip->DnsServerList.IpAddress;
      break;
    case 4:                      // Node Type
      n = (int)fip->NodeType;
      break;
    case 5:                      // Scope ID ???
      p = fip->ScopeId;
      break;
    case 6:                      // Routing enabled
      n = (int)fip->EnableRouting;
      break;
    case 7:                      // Proxy enabled
      n = (int)fip->EnableProxy;
      break;
    case 8:                      // DNS enabled
      n = (int)fip->EnableDns;
      break;
    // ADAPTERS INFO
    case 10:                    // Name
      p = adp->AdapterName;
      break;
    case 11:                    // Description
      if ((p = strstr(adp->Description, " - Packet Scheduler Miniport"))) {
        strncpy(buf, adp->Description, p - adp->Description);
        i = (int)(p - adp->Description);
        strncpy(buf, adp->Description, i);
        buf[i] = 0;
        p = buf;
      } else if ((p = strstr(adp->Description,
                  " - Miniport d'ordonnancement de paquets"))) {
        i = (int)(p - adp->Description);
        strncpy(buf, adp->Description, i);
        buf[i] = 0;
        p = buf;
      } else
        p = adp->Description;

      break;
    case 12:                    // MAC Address
      for (p = buf, i = 0; i < adp->AddressLength; i++) {
        if (i)
          strcat(p++, "-");
      
        p += sprintf(p, "%.2X", adp->Address[i]);
        } // endfor i

      p = buf;
      break;
    case 13:                    // Type
#if 0                        // This is not found in the SDK
      switch (adp->Type) {
        case IF_ETHERNET_ADAPTERTYPE:   p = "Ethernet Adapter";      break;
        case IF_TOKEN_RING_ADAPTERTYPE: p = "Token Ring Adapter";   break;
        case IF_FDDI_ADAPTERTYPE:       p = "FDDI Adapter";          break;
        case IF_PPP_ADAPTERTYPE:         p = "PPP Adapter";          break;
        case IF_LOOPBACK_ADAPTERTYPE:   p = "Loop Back Adapter";    break;
//      case IF_SLIP_ADAPTERTYPE:        p = "Generic Slip Adapter";  break;
        default:
          sprintf(buf, "Other Adapter, type=%d", adp->Type);
          p = buf;
        } // endswitch Type
#endif // 0
      n = (int)adp->Type;
      break;
    case 14:                    // DHCP enabled
      n = (int)adp->DhcpEnabled;
      break;
    case 15:                    // IP Address
      p = (adp->CurrentIpAddress)
        ? (char*)&adp->CurrentIpAddress->IpAddress
        : (char*)&adp->IpAddressList.IpAddress;
      break;
    case 16:                    // Subnet Mask
      p = (adp->CurrentIpAddress)
        ? (char*)&adp->CurrentIpAddress->IpMask
        : (char*)&adp->IpAddressList.IpMask;
      break;
    case 17:                    // Gateway
      p = (char*)&adp->GatewayList.IpAddress;
      break;
    case 18:                    // DHCP Server
      p = (char*)&adp->DhcpServer.IpAddress;
      break;
    case 19:                    // Have WINS
      n = (adp->HaveWins) ? 1 : 0;
      break;
    case 20:                    // Primary WINS
      p = (char*)&adp->PrimaryWinsServer.IpAddress;
      break;
    case 21:                    // Secondary WINS
      p = (char*)&adp->SecondaryWinsServer.IpAddress;
      break;
    case 22:                    // Lease obtained
      n = (int)adp->LeaseObtained;
      break;
    case 23:                    // Lease expires
      n = (int)adp->LeaseExpires;
      break;
    default:
      if (Buf_Type == TYPE_STRING) {
        sprintf(buf, "Invalid flag value %d", Flag);
        p = buf;
      } else
        n = 0;

    } // endswitch Flag

  if (p)
    Value->SetValue_psz(p);
  else
    Value->SetValue(n);

  } // end of ReadColumn