/* Copyright (C) 2003 Juan Lang
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Implementation notes
 * Interface index fun:
 * - Windows may rely on an index being cleared in the topmost 8 bits in some
 *   APIs; see GetFriendlyIfIndex and the mention of "backward compatible"
 *   indexes.  It isn't clear which APIs might fail with non-backward-compatible
 *   indexes, but I'll keep them bits clear just in case.
 * - Even though if_nametoindex and if_indextoname seem to be pretty portable,
 *   Linux, at any rate, uses the same interface index for all virtual
 *   interfaces of a real interface as well as for the real interface itself.
 *   If I used the Linux index as my index, this would break my statement that
 *   an index is a key, and that an interface has 0 or 1 IP addresses.
 *   If that behavior were consistent across UNIXen (I don't know), it could
 *   help me implement multiple IP addresses more in the Windows way.
 * I used to assert I could not use UNIX interface indexes as my iphlpapi
 * indexes due to restrictions in netapi32 and wsock32, but I have removed
 * those restrictions, so using if_nametoindex and if_indextoname rather
 * than my current mess would probably be better.
 * FIXME:
 * - I don't support IPv6 addresses here, since SIOCGIFCONF can't return them
 *
 * There are three implemened methods for determining the MAC address of an
 * interface:
 * - a specific IOCTL (Linux)
 * - looking in the ARP cache (at least Solaris)
 * - using the sysctl interface (FreeBSD and MacOSX)
 * Solaris and some others have SIOCGENADDR, but I haven't gotten that to work
 * on the Solaris boxes at SourceForge's compile farm, whereas SIOCGARP does.
 */

#include "config.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <sys/types.h>

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif

#ifdef HAVE_NET_IF_ARP_H
#include <net/if_arp.h>
#endif

#ifdef HAVE_NET_ROUTE_H
#include <net/route.h>
#endif

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif

#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif

#ifdef HAVE_NET_IF_DL_H
#include <net/if_dl.h>
#endif

#ifdef HAVE_NET_IF_TYPES_H
#include <net/if_types.h>
#endif

#include "ifenum.h"

#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
#define ifreq_len(ifr) \
 max(sizeof(struct ifreq), sizeof((ifr)->ifr_name)+(ifr)->ifr_addr.sa_len)
#else
#define ifreq_len(ifr) sizeof(struct ifreq)
#endif

#ifndef ETH_ALEN
#define ETH_ALEN 6
#endif

#ifndef INADDR_NONE
#define INADDR_NONE (~0U)
#endif

#define INITIAL_INTERFACES_ASSUMED 4

#define INDEX_IS_LOOPBACK 0x00800000

/* Type declarations */

typedef struct _InterfaceNameMapEntry {
  char  name[IFNAMSIZ];
  BOOL  inUse;
  BOOL  usedLastPass;
} InterfaceNameMapEntry;

typedef struct _InterfaceNameMap {
  DWORD numInterfaces;
  DWORD nextAvailable;
  DWORD numAllocated;
  InterfaceNameMapEntry table[1];
} InterfaceNameMap;

/* Global variables */

static CRITICAL_SECTION mapCS;
static InterfaceNameMap *gNonLoopbackInterfaceMap = NULL;
static InterfaceNameMap *gLoopbackInterfaceMap = NULL;

/* Functions */

void interfaceMapInit(void)
{
    InitializeCriticalSection(&mapCS);
}

void interfaceMapFree(void)
{
    DeleteCriticalSection(&mapCS);
    HeapFree(GetProcessHeap(), 0, gNonLoopbackInterfaceMap);
    HeapFree(GetProcessHeap(), 0, gLoopbackInterfaceMap);
}

/* Sizes the passed-in map to have enough space for numInterfaces interfaces.
 * If map is NULL, allocates a new map.  If it is not, may reallocate the
 * existing map and return a map of increased size.  Returns the allocated map,
 * or NULL if it could not allocate a map of the requested size.
 */
static InterfaceNameMap *sizeMap(InterfaceNameMap *map, DWORD numInterfaces)
{
  if (!map) {
    numInterfaces = max(numInterfaces, INITIAL_INTERFACES_ASSUMED);
    map = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
     sizeof(InterfaceNameMap) +
     (numInterfaces - 1) * sizeof(InterfaceNameMapEntry));
    if (map)
      map->numAllocated = numInterfaces;
  }
  else {
    if (map->numAllocated < numInterfaces) {
      map = HeapReAlloc(GetProcessHeap(), 0, map,
       sizeof(InterfaceNameMap) +
       (numInterfaces - 1) * sizeof(InterfaceNameMapEntry));
      if (map)
        memset(&map->table[map->numAllocated], 0,
         (numInterfaces - map->numAllocated) * sizeof(InterfaceNameMapEntry));
    }
  }
  return map;
}

static int isLoopbackInterface(int fd, const char *name)
{
  int ret = 0;

  if (name) {
    struct ifreq ifr;

    lstrcpynA(ifr.ifr_name, name, IFNAMSIZ);
    if (ioctl(fd, SIOCGIFFLAGS, &ifr) == 0)
      ret = ifr.ifr_flags & IFF_LOOPBACK;
  }
  return ret;
}

static void countInterfaces(int fd, caddr_t buf, size_t len)
{
  caddr_t ifPtr = buf;
  DWORD numNonLoopbackInterfaces = 0, numLoopbackInterfaces = 0;

  while (ifPtr && ifPtr < buf + len) {
    struct ifreq *ifr = (struct ifreq *)ifPtr;

    if (isLoopbackInterface(fd, ifr->ifr_name))
      numLoopbackInterfaces++;
    else
      numNonLoopbackInterfaces++;
    ifPtr += ifreq_len(ifr);
  }
  gNonLoopbackInterfaceMap = sizeMap(gNonLoopbackInterfaceMap,
   numNonLoopbackInterfaces);
  gLoopbackInterfaceMap = sizeMap(gLoopbackInterfaceMap,
   numLoopbackInterfaces);
}

/* Stores the name in the given map, and increments the map's numInterfaces
 * member if stored successfully.  Will store in the same slot as previously if
 * usedLastPass is set, otherwise will store in a new slot.
 * Assumes map and name are not NULL, and the usedLastPass flag is set
 * correctly for each entry in the map, and that map->numInterfaces <
 * map->numAllocated.
 * FIXME: this is kind of expensive, doing a linear scan of the map with a
 * string comparison of each entry to find the old slot.
 */
static void storeInterfaceInMap(InterfaceNameMap *map, const char *name)
{
  if (map && name) {
    DWORD ndx;
    BOOL stored = FALSE;

    /* look for previous slot, mark in use if so */
    for (ndx = 0; !stored && ndx < map->nextAvailable; ndx++) {
      if (map->table[ndx].usedLastPass && !strncmp(map->table[ndx].name, name,
       sizeof(map->table[ndx].name))) {
        map->table[ndx].inUse = TRUE;
        stored = TRUE;
      }
    }
    /* look for new slot */
    for (ndx = 0; !stored && ndx < map->numAllocated; ndx++) {
      if (!map->table[ndx].inUse) {
        lstrcpynA(map->table[ndx].name, name, IFNAMSIZ);
        map->table[ndx].inUse = TRUE;
        stored = TRUE;
        if (ndx >= map->nextAvailable)
          map->nextAvailable = ndx + 1;
      }
    }
    if (stored)
      map->numInterfaces++;
  }
}

/* Sets all used entries' usedLastPass flag to their inUse flag, clears
 * their inUse flag, and clears their numInterfaces member.
 */
static void markOldInterfaces(InterfaceNameMap *map)
{
  if (map) {
    DWORD ndx;

    map->numInterfaces = 0;
    for (ndx = 0; ndx < map->nextAvailable; ndx++) {
      map->table[ndx].usedLastPass = map->table[ndx].inUse;
      map->table[ndx].inUse = FALSE;
    }
  }
}

static void classifyInterfaces(int fd, caddr_t buf, size_t len)
{
  caddr_t ifPtr = buf;

  markOldInterfaces(gNonLoopbackInterfaceMap);
  markOldInterfaces(gLoopbackInterfaceMap);
  while (ifPtr && ifPtr < buf + len) {
    struct ifreq *ifr = (struct ifreq *)ifPtr;

    if (ifr->ifr_addr.sa_family == AF_INET) {
      if (isLoopbackInterface(fd, ifr->ifr_name))
        storeInterfaceInMap(gLoopbackInterfaceMap, ifr->ifr_name);
      else
        storeInterfaceInMap(gNonLoopbackInterfaceMap, ifr->ifr_name);
    }
    ifPtr += ifreq_len(ifr);
  }
}

static void enumerateInterfaces(void)
{
  int fd;

  fd = socket(PF_INET, SOCK_DGRAM, 0);
  if (fd != -1) {
    int ret, guessedNumInterfaces;
    struct ifconf ifc;

    /* try to avoid silly heap action by starting with the right size buffer */
    guessedNumInterfaces = 0;
    if (gNonLoopbackInterfaceMap)
      guessedNumInterfaces += gNonLoopbackInterfaceMap->numInterfaces;
    if (gLoopbackInterfaceMap)
      guessedNumInterfaces += gLoopbackInterfaceMap->numInterfaces;

    ret = 0;
    memset(&ifc, 0, sizeof(ifc));
    /* there is no way to know the interface count beforehand,
       so we need to loop again and again upping our max each time
       until returned < max */
    do {
      if (guessedNumInterfaces == 0)
        guessedNumInterfaces = INITIAL_INTERFACES_ASSUMED;
      else
        guessedNumInterfaces *= 2;
      HeapFree(GetProcessHeap(), 0, ifc.ifc_buf);
      ifc.ifc_len = sizeof(struct ifreq) * guessedNumInterfaces;
      ifc.ifc_buf = HeapAlloc(GetProcessHeap(), 0, ifc.ifc_len);
      ret = ioctl(fd, SIOCGIFCONF, &ifc);
    } while (ret == 0 &&
     ifc.ifc_len == (sizeof(struct ifreq) * guessedNumInterfaces));

    if (ret == 0) {
      EnterCriticalSection(&mapCS);
      countInterfaces(fd, ifc.ifc_buf, ifc.ifc_len);
      classifyInterfaces(fd, ifc.ifc_buf, ifc.ifc_len);
      LeaveCriticalSection(&mapCS);
    }

    HeapFree(GetProcessHeap(), 0, ifc.ifc_buf);
    close(fd);
  }
}

DWORD getNumNonLoopbackInterfaces(void)
{
  enumerateInterfaces();
  return gNonLoopbackInterfaceMap ? gNonLoopbackInterfaceMap->numInterfaces : 0;
}

DWORD getNumInterfaces(void)
{
  DWORD ret = getNumNonLoopbackInterfaces();

  ret += gLoopbackInterfaceMap ? gLoopbackInterfaceMap->numInterfaces : 0;
  return ret;
}

const char *getInterfaceNameByIndex(DWORD index)
{
  DWORD realIndex;
  InterfaceNameMap *map;
  const char *ret = NULL;

  EnterCriticalSection(&mapCS);
  if (index & INDEX_IS_LOOPBACK) {
    realIndex = index ^ INDEX_IS_LOOPBACK;
    map = gLoopbackInterfaceMap;
  }
  else {
    realIndex = index;
    map = gNonLoopbackInterfaceMap;
  }
  if (map && realIndex < map->nextAvailable)
    ret = map->table[realIndex].name;
  LeaveCriticalSection(&mapCS);
  return ret;
}

DWORD getInterfaceIndexByName(const char *name, PDWORD index)
{
  DWORD ndx, ret;
  BOOL found = FALSE;

  if (!name)
    return ERROR_INVALID_PARAMETER;
  if (!index)
    return ERROR_INVALID_PARAMETER;

  EnterCriticalSection(&mapCS);
  for (ndx = 0; !found && gNonLoopbackInterfaceMap &&
   ndx < gNonLoopbackInterfaceMap->nextAvailable; ndx++)
    if (!strncmp(gNonLoopbackInterfaceMap->table[ndx].name, name, IFNAMSIZ)) {
      found = TRUE;
      *index = ndx;
    }
  for (ndx = 0; !found && gLoopbackInterfaceMap &&
   ndx < gLoopbackInterfaceMap->nextAvailable; ndx++)
    if (!strncmp(gLoopbackInterfaceMap->table[ndx].name, name, IFNAMSIZ)) {
      found = TRUE;
      *index = ndx | INDEX_IS_LOOPBACK;
    }
  LeaveCriticalSection(&mapCS);
  if (found)
    ret = NO_ERROR;
  else
    ret = ERROR_INVALID_DATA;
  return ret;
}

static void addMapEntriesToIndexTable(InterfaceIndexTable *table,
 const InterfaceNameMap *map)
{
  if (table && map) {
    DWORD ndx;

    for (ndx = 0; ndx < map->nextAvailable &&
     table->numIndexes < table->numAllocated; ndx++)
      if (map->table[ndx].inUse) {
        DWORD externalNdx = ndx;

        if (map == gLoopbackInterfaceMap)
          externalNdx |= INDEX_IS_LOOPBACK;
        table->indexes[table->numIndexes++] = externalNdx;
      }
  }
}

InterfaceIndexTable *getInterfaceIndexTable(void)
{
  DWORD numInterfaces;
  InterfaceIndexTable *ret;
 
  EnterCriticalSection(&mapCS);
  numInterfaces = getNumInterfaces();
  ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
   sizeof(InterfaceIndexTable) + (numInterfaces - 1) * sizeof(DWORD));
  if (ret) {
    ret->numAllocated = numInterfaces;
    addMapEntriesToIndexTable(ret, gNonLoopbackInterfaceMap);
    addMapEntriesToIndexTable(ret, gLoopbackInterfaceMap);
  }
  LeaveCriticalSection(&mapCS);
  return ret;
}

InterfaceIndexTable *getNonLoopbackInterfaceIndexTable(void)
{
  DWORD numInterfaces;
  InterfaceIndexTable *ret;

  EnterCriticalSection(&mapCS);
  numInterfaces = getNumNonLoopbackInterfaces();
  ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
   sizeof(InterfaceIndexTable) + (numInterfaces - 1) * sizeof(DWORD));
  if (ret) {
    ret->numAllocated = numInterfaces;
    addMapEntriesToIndexTable(ret, gNonLoopbackInterfaceMap);
  }
  LeaveCriticalSection(&mapCS);
  return ret;
}

DWORD getInterfaceIPAddrByName(const char *name)
{
  DWORD ret = INADDR_ANY;

  if (name) {
    int fd = socket(PF_INET, SOCK_DGRAM, 0);

    if (fd != -1) {
      struct ifreq ifr;

      lstrcpynA(ifr.ifr_name, name, IFNAMSIZ);
      if (ioctl(fd, SIOCGIFADDR, &ifr) == 0)
        memcpy(&ret, ifr.ifr_addr.sa_data + 2, sizeof(DWORD));
      close(fd);
    }
  }
  return ret;
}

DWORD getInterfaceIPAddrByIndex(DWORD index)
{
  DWORD ret;
  const char *name = getInterfaceNameByIndex(index);

  if (name)
    ret = getInterfaceIPAddrByName(name);
  else
    ret = INADDR_ANY;
  return ret;
}

DWORD getInterfaceBCastAddrByName(const char *name)
{
  DWORD ret = INADDR_ANY;

  if (name) {
    int fd = socket(PF_INET, SOCK_DGRAM, 0);

    if (fd != -1) {
      struct ifreq ifr;

      lstrcpynA(ifr.ifr_name, name, IFNAMSIZ);
      if (ioctl(fd, SIOCGIFBRDADDR, &ifr) == 0)
        memcpy(&ret, ifr.ifr_addr.sa_data + 2, sizeof(DWORD));
      close(fd);
    }
  }
  return ret;
}

DWORD getInterfaceBCastAddrByIndex(DWORD index)
{
  DWORD ret;
  const char *name = getInterfaceNameByIndex(index);

  if (name)
    ret = getInterfaceBCastAddrByName(name);
  else
    ret = INADDR_ANY;
  return ret;
}

DWORD getInterfaceMaskByName(const char *name)
{
  DWORD ret = INADDR_NONE;

  if (name) {
    int fd = socket(PF_INET, SOCK_DGRAM, 0);

    if (fd != -1) {
      struct ifreq ifr;

      lstrcpynA(ifr.ifr_name, name, IFNAMSIZ);
      if (ioctl(fd, SIOCGIFNETMASK, &ifr) == 0)
        memcpy(&ret, ifr.ifr_addr.sa_data + 2, sizeof(DWORD));
      close(fd);
    }
  }
  return ret;
}

DWORD getInterfaceMaskByIndex(DWORD index)
{
  DWORD ret;
  const char *name = getInterfaceNameByIndex(index);

  if (name)
    ret = getInterfaceMaskByName(name);
  else
    ret = INADDR_NONE;
  return ret;
}

#if defined (SIOCGIFHWADDR)
DWORD getInterfacePhysicalByName(const char *name, PDWORD len, PBYTE addr,
 PDWORD type)
{
  DWORD ret;
  int fd;

  if (!name || !len || !addr || !type)
    return ERROR_INVALID_PARAMETER;

  fd = socket(PF_INET, SOCK_DGRAM, 0);
  if (fd != -1) {
    struct ifreq ifr;

    memset(&ifr, 0, sizeof(struct ifreq));
    lstrcpynA(ifr.ifr_name, name, IFNAMSIZ);
    if ((ioctl(fd, SIOCGIFHWADDR, &ifr)))
      ret = ERROR_INVALID_DATA;
    else {
      unsigned int addrLen;

      switch (ifr.ifr_hwaddr.sa_family)
      {
#ifdef ARPHRD_LOOPBACK
        case ARPHRD_LOOPBACK:
          addrLen = 0;
          *type = MIB_IF_TYPE_LOOPBACK;
          break;
#endif
#ifdef ARPHRD_ETHER
        case ARPHRD_ETHER:
          addrLen = ETH_ALEN;
          *type = MIB_IF_TYPE_ETHERNET;
          break;
#endif
#ifdef ARPHRD_FDDI
        case ARPHRD_FDDI:
          addrLen = ETH_ALEN;
          *type = MIB_IF_TYPE_FDDI;
          break;
#endif
#ifdef ARPHRD_IEEE802
        case ARPHRD_IEEE802: /* 802.2 Ethernet && Token Ring, guess TR? */
          addrLen = ETH_ALEN;
          *type = MIB_IF_TYPE_TOKENRING;
          break;
#endif
#ifdef ARPHRD_IEEE802_TR
        case ARPHRD_IEEE802_TR: /* also Token Ring? */
          addrLen = ETH_ALEN;
          *type = MIB_IF_TYPE_TOKENRING;
          break;
#endif
#ifdef ARPHRD_SLIP
        case ARPHRD_SLIP:
          addrLen = 0;
          *type = MIB_IF_TYPE_SLIP;
          break;
#endif
#ifdef ARPHRD_PPP
        case ARPHRD_PPP:
          addrLen = 0;
          *type = MIB_IF_TYPE_PPP;
          break;
#endif
        default:
          addrLen = min(MAX_INTERFACE_PHYSADDR, sizeof(ifr.ifr_hwaddr.sa_data));
          *type = MIB_IF_TYPE_OTHER;
      }
      if (addrLen > *len) {
        ret = ERROR_INSUFFICIENT_BUFFER;
        *len = addrLen;
      }
      else {
        if (addrLen > 0)
          memcpy(addr, ifr.ifr_hwaddr.sa_data, addrLen);
        /* zero out remaining bytes for broken implementations */
        memset(addr + addrLen, 0, *len - addrLen);
        *len = addrLen;
        ret = NO_ERROR;
      }
    }
    close(fd);
  }
  else
    ret = ERROR_NO_MORE_FILES;
  return ret;
}
#elif defined (SIOCGARP)
DWORD getInterfacePhysicalByName(const char *name, PDWORD len, PBYTE addr,
 PDWORD type)
{
  DWORD ret;
  int fd;

  if (!name || !len || !addr || !type)
    return ERROR_INVALID_PARAMETER;

  fd = socket(PF_INET, SOCK_DGRAM, 0);
  if (fd != -1) {
    if (isLoopbackInterface(fd, name)) {
      *type = MIB_IF_TYPE_LOOPBACK;
      memset(addr, 0, *len);
      *len = 0;
      ret=NOERROR;
    }
    else {
      struct arpreq arp;
      struct sockaddr_in *saddr;

      memset(&arp, 0, sizeof(struct arpreq));
      arp.arp_pa.sa_family = AF_INET;
      saddr = (struct sockaddr_in *)&arp; /* proto addr is first member */
      saddr->sin_family = AF_INET;
      saddr->sin_addr.s_addr = getInterfaceIPAddrByName(name);
      if ((ioctl(fd, SIOCGARP, &arp)))
        ret = ERROR_INVALID_DATA;
      else {
        /* FIXME:  heh:  who said it was ethernet? */
        int addrLen = ETH_ALEN;

        if (addrLen > *len) {
          ret = ERROR_INSUFFICIENT_BUFFER;
          *len = addrLen;
        }
        else {
          if (addrLen > 0)
            memcpy(addr, &arp.arp_ha.sa_data[0], addrLen);
          /* zero out remaining bytes for broken implementations */
          memset(addr + addrLen, 0, *len - addrLen);
          *len = addrLen;
          *type = MIB_IF_TYPE_ETHERNET;
          ret = NO_ERROR;
        }
      }
    }
    close(fd);
  }
    else
      ret = ERROR_NO_MORE_FILES;

  return ret;
}
#elif defined (HAVE_SYS_SYSCTL_H) && defined (HAVE_NET_IF_DL_H)
DWORD getInterfacePhysicalByName(const char *name, PDWORD len, PBYTE addr,
 PDWORD type)
{
  DWORD ret;
  struct if_msghdr *ifm;
  struct sockaddr_dl *sdl;
  u_char *p, *buf;
  size_t mibLen;
  int mib[] = { CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, 0 };
  int addrLen;
  BOOL found = FALSE;

  if (!name || !len || !addr || !type)
    return ERROR_INVALID_PARAMETER;

  if (sysctl(mib, 6, NULL, &mibLen, NULL, 0) < 0)
    return ERROR_NO_MORE_FILES;

  buf = HeapAlloc(GetProcessHeap(), 0, mibLen);
  if (!buf)
    return ERROR_NOT_ENOUGH_MEMORY;

  if (sysctl(mib, 6, buf, &mibLen, NULL, 0) < 0) {
    HeapFree(GetProcessHeap(), 0, buf);
    return ERROR_NO_MORE_FILES;
  }

  ret = ERROR_INVALID_DATA;
  for (p = buf; !found && p < buf + mibLen; p += ifm->ifm_msglen) {
    ifm = (struct if_msghdr *)p;
    sdl = (struct sockaddr_dl *)(ifm + 1);

    if (ifm->ifm_type != RTM_IFINFO || (ifm->ifm_addrs & RTA_IFP) == 0)
      continue;

    if (sdl->sdl_family != AF_LINK || sdl->sdl_nlen == 0 ||
     memcmp(sdl->sdl_data, name, max(sdl->sdl_nlen, strlen(name))) != 0)
      continue;

    found = TRUE;
    addrLen = min(MAX_INTERFACE_PHYSADDR, sdl->sdl_alen);
    if (addrLen > *len) {
      ret = ERROR_INSUFFICIENT_BUFFER;
      *len = addrLen;
    }
    else {
      if (addrLen > 0)
        memcpy(addr, LLADDR(sdl), addrLen);
      /* zero out remaining bytes for broken implementations */
      memset(addr + addrLen, 0, *len - addrLen);
      *len = addrLen;
#if defined(HAVE_NET_IF_TYPES_H)
      switch (sdl->sdl_type)
      {
        case IFT_ETHER:
          *type = MIB_IF_TYPE_ETHERNET;
          break;
        case IFT_FDDI:
          *type = MIB_IF_TYPE_FDDI;
          break;
        case IFT_ISO88024: /* Token Bus */
          *type = MIB_IF_TYPE_TOKENRING;
          break;
        case IFT_ISO88025: /* Token Ring */
          *type = MIB_IF_TYPE_TOKENRING;
          break;
        case IFT_PPP:
          *type = MIB_IF_TYPE_PPP;
          break;
        case IFT_SLIP:
          *type = MIB_IF_TYPE_SLIP;
          break;
        case IFT_LOOP:
          *type = MIB_IF_TYPE_LOOPBACK;
          break;
        default:
          *type = MIB_IF_TYPE_OTHER;
      }
#else
      /* default if we don't know */
      *type = MIB_IF_TYPE_ETHERNET;
#endif
      ret = NO_ERROR;
    }
  }
  HeapFree(GetProcessHeap(), 0, buf);
  return ret;
}
#endif

DWORD getInterfacePhysicalByIndex(DWORD index, PDWORD len, PBYTE addr,
 PDWORD type)
{
  const char *name = getInterfaceNameByIndex(index);

  if (name)
    return getInterfacePhysicalByName(name, len, addr, type);
  else
    return ERROR_INVALID_DATA;
}

DWORD getInterfaceMtuByName(const char *name, PDWORD mtu)
{
  DWORD ret;
  int fd;

  if (!name)
    return ERROR_INVALID_PARAMETER;
  if (!mtu)
    return ERROR_INVALID_PARAMETER;

  fd = socket(PF_INET, SOCK_DGRAM, 0);
  if (fd != -1) {
    struct ifreq ifr;

    lstrcpynA(ifr.ifr_name, name, IFNAMSIZ);
    if ((ioctl(fd, SIOCGIFMTU, &ifr)))
      ret = ERROR_INVALID_DATA;
    else {
#ifndef __sun
      *mtu = ifr.ifr_mtu;
#else
      *mtu = ifr.ifr_metric;
#endif
      ret = NO_ERROR;
    }
  }
  else
    ret = ERROR_NO_MORE_FILES;
  return ret;
}

DWORD getInterfaceMtuByIndex(DWORD index, PDWORD mtu)
{
  const char *name = getInterfaceNameByIndex(index);

  if (name)
    return getInterfaceMtuByName(name, mtu);
  else
    return ERROR_INVALID_DATA;
}

DWORD getInterfaceStatusByName(const char *name, PDWORD status)
{
  DWORD ret;
  int fd;

  if (!name)
    return ERROR_INVALID_PARAMETER;
  if (!status)
    return ERROR_INVALID_PARAMETER;

  fd = socket(PF_INET, SOCK_DGRAM, 0);
  if (fd != -1) {
    struct ifreq ifr;

    lstrcpynA(ifr.ifr_name, name, IFNAMSIZ);
    if ((ioctl(fd, SIOCGIFFLAGS, &ifr)))
      ret = ERROR_INVALID_DATA;
    else {
      if (ifr.ifr_flags & IFF_UP)
        *status = MIB_IF_OPER_STATUS_OPERATIONAL;
      else
        *status = MIB_IF_OPER_STATUS_NON_OPERATIONAL;
      ret = NO_ERROR;
    }
  }
  else
    ret = ERROR_NO_MORE_FILES;
  return ret;
}

DWORD getInterfaceStatusByIndex(DWORD index, PDWORD status)
{
  const char *name = getInterfaceNameByIndex(index);

  if (name)
    return getInterfaceStatusByName(name, status);
  else
    return ERROR_INVALID_DATA;
}

DWORD getInterfaceEntryByName(const char *name, PMIB_IFROW entry)
{
  BYTE addr[MAX_INTERFACE_PHYSADDR];
  DWORD ret, len = sizeof(addr), type;

  if (!name)
    return ERROR_INVALID_PARAMETER;
  if (!entry)
    return ERROR_INVALID_PARAMETER;

  if (getInterfacePhysicalByName(name, &len, addr, &type) == NO_ERROR) {
    WCHAR *assigner;
    const char *walker;

    memset(entry, 0, sizeof(MIB_IFROW));
    for (assigner = entry->wszName, walker = name; *walker; 
     walker++, assigner++)
      *assigner = *walker;
    *assigner = 0;
    getInterfaceIndexByName(name, &entry->dwIndex);
    entry->dwPhysAddrLen = len;
    memcpy(entry->bPhysAddr, addr, len);
    memset(entry->bPhysAddr + len, 0, sizeof(entry->bPhysAddr) - len);
    entry->dwType = type;
    /* FIXME: how to calculate real speed? */
    getInterfaceMtuByName(name, &entry->dwMtu);
    /* lie, there's no "administratively down" here */
    entry->dwAdminStatus = MIB_IF_ADMIN_STATUS_UP;
    getInterfaceStatusByName(name, &entry->dwOperStatus);
    /* punt on dwLastChange? */
    entry->dwDescrLen = min(strlen(name), MAX_INTERFACE_DESCRIPTION - 1);
    memcpy(entry->bDescr, name, entry->dwDescrLen);
    entry->bDescr[entry->dwDescrLen] = '\0';
    entry->dwDescrLen++;
    ret = NO_ERROR;
  }
  else
    ret = ERROR_INVALID_DATA;
  return ret;
}

DWORD getInterfaceEntryByIndex(DWORD index, PMIB_IFROW entry)
{
  const char *name = getInterfaceNameByIndex(index);

  if (name)
    return getInterfaceEntryByName(name, entry);
  else
    return ERROR_INVALID_DATA;
}

char *toIPAddressString(unsigned int addr, char string[16])
{
  if (string) {
    struct in_addr iAddr;

    iAddr.s_addr = addr;
    /* extra-anal, just to make auditors happy */
    lstrcpynA(string, inet_ntoa(iAddr), 16);
  }
  return string;
}
