/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by the University of California,
 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
 * the University nor the names of its contributors may be used to endorse
 * or promote products derived from this software without specific prior
 * written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * tcpdump/Win32 functions for reading and parsing system's Ethernet
 * address file:
 *    '%SystemRoot%/drivers/etc/ethers'  (Win-NT+)
 * or '%Windir%/etc/ethers'              (Win-9x/ME)
 *
 * G. Vanem <gvanem@yahoo.no> 2012.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <netdissect-stdinc.h>

#include "ether.h"
#include "netdissect.h"
#include "addrtoname.h"

typedef struct ether_addr {
        unsigned char octet[ETHER_ADDR_LEN];
      } ether_address;

typedef struct ether_entry {
        ether_address      eth_addr;  /* MAC address */
        char               *name;     /* name of MAC-address */
        struct ether_entry *next;
      } ether_entry;

static struct ether_entry *eth0 = NULL;

/*
 * The reason to avoid using 'pcap_next_etherent()' in addrtoname.c
 * are several:
 *   1) wpcap.dll and 'pcap_next_etherent()' could have been built in
 *      debug-mode (-MDd) or release-mode (-MD) and tcpdump in
 *      the opposite model.
 *   2) If this is built by MSVC, wpcap.dll could have been built by
 *      MingW. It has no debug-model.
 *   3) It may not have been exported from wpcap.dll (present in wpcap.def).
 *
 * So we shoe-horn the building of tcpdump with '-DUSE_ETHER_NTOHOST' to
 * make 'init_etherarray()' call the below 'ether_ntohost()' instead.
 */
#if !defined(USE_ETHER_NTOHOST)
#error "'-DUSE_ETHER_NTOHOST' must be set"
#endif

/*
 * Return TRUE if running under Win-95/98/ME.
 */
static BOOL is_win9x (void)
{
  OSVERSIONINFO ovi;
  DWORD os_ver = GetVersion();
  DWORD major_ver = LOBYTE (LOWORD(os_ver));

  return (os_ver >= 0x80000000 && major_ver >= 4);
}

/*
 * Return path to "%SystemRoot%/drivers/etc/<file>"  (Win-NT+)
 *          or to "%Windir%/etc/<file>"              (Win-9x/ME)
 */
const char *etc_path (const char *file)
{
  BOOL win9x = is_win9x();
  const char *env = win9x ? getenv("WinDir") : getenv("SystemRoot");
  static char path[MAX_PATH];

  if (!env)
    return (file);

  if (win9x)
    snprintf (path, sizeof(path), "%s\\etc\\%s", env, file);
  else
    snprintf (path, sizeof(path), "%s\\system32\\drivers\\etc\\%s", env, file);

  return (path);
}

/*
 * Parse a string-buf containing an MAC address and name.
 * Accepts MAC addresses on both "xx:xx:xx.." and "xx-xx-xx.." forms.
 *
 * We could have used pcap_ether_aton(), but problem 3) above could apply.
 * or we could have cut & pasted 'pcap_next_etherent(FILE *fp)' below.
 */
#define MIN_LEN  sizeof("0:0:0:0:0:0 X")

static
int parse_ether_buf (const char *buf, char **result, struct ether_addr *e)
{
  const char *fmt;
  char       *name;
  char       *str = (char*)buf;
  unsigned    eth [sizeof(*e)];
  int         i;

  /* Find first non-blank in 'buf' */
  while (str[0] && str[1] && isspace((int)str[0]))
       str++;

  if (*str == '#' || *str == ';' || *str == '\n' || strlen(str) < MIN_LEN)
     return (0);

  if (str[2] == ':')
    fmt = "%02x:%02x:%02x:%02x:%02x:%02x";
  else
    fmt = "%02x-%02x-%02x-%02x-%02x-%02x";

  if (sscanf(str, fmt, &eth[0], &eth[1], &eth[2], &eth[3], &eth[4], &eth[5]) != ETHER_ADDR_LEN)
     return (0);

  str  = strtok (str, " \t");
  name = strtok (NULL, " #\t\n");

  if (!str || !name || strlen(name) < 1)
     return (0);

  *result = name;

  for (i = 0; i < ETHER_ADDR_LEN; i++)
      e->octet[i] = eth[i];

  return (1);
}

static void free_ethers (void)
{
  struct ether_entry *e, *next;

  for (e = eth0; e; e = next) {
    next = e->next;
    free(e->name);
    free(e);
  }
  eth0 = NULL;
}

static int init_ethers (void)
{
  char  buf[BUFSIZE];
  FILE *fp = fopen (etc_path("ethers"), "r");

  if (!fp)
     return (0);

  while (fgets(buf,sizeof(buf),fp))
  {
    struct ether_entry *e;
    char  *name;
    ether_address eth;

    if (!parse_ether_buf(buf,&name,&eth))
       continue;

    e = calloc (sizeof(*e), 1);
    if (!e)
       break;

    memcpy(&e->eth_addr, &eth, ETHER_ADDR_LEN);
    e->name = strdup(name);
    if (!e->name) {
      free(e);
      break;
    }

    e->next = eth0;
    eth0 = e;
  }
  fclose(fp);
  atexit(free_ethers);
  return (1);
}

/*
 * Map an ethernet address 'e' to a 'name'.
 * Returns 0 on success.
 *
 * This function is called at startup by init_etherarray() and then
 * by etheraddr_string() as needed. To avoid doing an expensive fopen()
 * on each call, the contents of 'etc_path("ethers")' is cached here in
 * a linked-list 'eth0'.
 */
int ether_ntohost (char *name, struct ether_addr *e)
{
  const struct ether_entry *cache;
  static int init = 0;

  if (!init) {
    init_ethers();
    init = 1;
  }

  for (cache = eth0; cache; cache = cache->next)
     if (!memcmp(&e->octet, &cache->eth_addr, ETHER_ADDR_LEN)) {
       strcpy (name,cache->name);
       return (0);
     }
  return (1);
}