/*
 *  IP MIB group implementation - ip.c
 *
 */

#include <config.h>

#if defined(IFNET_NEEDS_KERNEL) && !defined(_KERNEL)
#define _KERNEL 1
#define _I_DEFINED_KERNEL
#endif
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif

#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <sys/types.h>
#if HAVE_WINSOCK_H
#include <winsock.h>
#endif
#if HAVE_SYS_SYSCTL_H
#ifdef _I_DEFINED_KERNEL
#undef _KERNEL
#endif
#include <sys/sysctl.h>
#ifdef _I_DEFINED_KERNEL
#define _KERNEL 1
#endif
#endif
#if HAVE_SYS_SYSMP_H
#include <sys/sysmp.h>
#endif
#if HAVE_SYS_TCPIPSTATS_H
#include <sys/tcpipstats.h>
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_NET_IF_H
#include <net/if.h>
#endif
#if HAVE_NET_IF_VAR_H
#include <net/if_var.h>
#endif
#ifdef _I_DEFINED_KERNEL
#undef _KERNEL
#endif
#if HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#if HAVE_SYS_HASHING_H
#include <sys/hashing.h>
#endif
#if HAVE_NETINET_IN_VAR_H
#include <netinet/in_var.h>
#endif
#if HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#if HAVE_NETINET_IP_VAR_H
#include <netinet/ip_var.h>
#endif
#if HAVE_INET_MIB2_H
#include <inet/mib2.h>
#endif
#if HAVE_SYS_STREAM_H
#include <sys/stream.h>
#endif
#if HAVE_NET_ROUTE_H
#include <net/route.h>
#endif
#if HAVE_SYSLOG_H
#include <syslog.h>
#endif

#ifdef solaris2
#include "kernel_sunos5.h"
#else
#include "kernel.h"
#endif

#include "mibincl.h"
#include "system.h"
#include "auto_nlist.h"

#ifdef MIB_IPCOUNTER_SYMBOL
#include <sys/mib.h>
#include <netinet/mib_kern.h>
#endif /* MIB_IPCOUNTER_SYMBOL */

#include "ip.h"
#include "interfaces.h"
#include "sysORTable.h"

#ifdef cygwin
#define WIN32
#include <windows.h>
#endif

	/*********************
	 *
	 *  Kernel & interface information,
	 *   and internal forward declarations
	 *
	 *********************/

	/*********************
	 *
	 *  Initialisation & common implementation functions
	 *
	 *********************/

	/*********************
	 *
	 *  System specific implementation functions
	 *
	 *********************/

#ifndef WIN32

#if !defined(CAN_USE_SYSCTL) || !defined(IPCTL_STATS)
#ifndef solaris2

#ifdef freebsd2
static void Address_Scan_Init (void);
static int Address_Scan_Next (short *, struct in_ifaddr *);
#endif

/*
  var_ipAddrEntry(...
  Arguments:
  vp	  IN      - pointer to variable entry that points here
  name    IN/OUT  - IN/name requested, OUT/name found
  length  IN/OUT  - length of IN/OUT oid's 
  exact   IN      - TRUE if an exact match was requested
  var_len OUT     - length of variable or 0 if function returned
  write_method
  
*/

u_char *
var_ipAddrEntry(struct variable *vp,
		oid *name,
		size_t *length,
		int exact,
		size_t *var_len,
		WriteMethod **write_method)
{
    /*
     * object identifier is of form:
     * 1.3.6.1.2.1.4.20.1.?.A.B.C.D,  where A.B.C.D is IP address.
     * IPADDR starts at offset 10.
     */
    oid			    lowest[14];
    oid			    current[14], *op;
    u_char		    *cp;
    int			    lowinterface=0;
#ifndef freebsd2
    short                   interface;
#endif
    static struct in_ifaddr in_ifaddr;
#if !defined(linux) && !defined(sunV3)
    static struct in_ifaddr lowin_ifaddr;
#else
    static struct ifnet lowin_ifnet;
#endif
    static struct ifnet ifnet;

    /* fill in object part of name for current (less sizeof instance part) */

    memcpy( (char *)current,(char *)vp->name, (int)vp->namelen * sizeof(oid));

#ifndef freebsd2
    Interface_Scan_Init();
#else
    Address_Scan_Init();
#endif
    for (;;) {

#ifndef freebsd2
	if (Interface_Scan_Next(&interface, NULL, &ifnet, &in_ifaddr) == 0)
	    break;
#ifdef STRUCT_IFNET_HAS_IF_ADDRLIST
	if ( ifnet.if_addrlist == 0 )
	    continue;                   /* No address found for interface */
#endif
#else
	if (Address_Scan_Next(&interface, &in_ifaddr) == 0)
	    break;
#endif /* freebsd2 */
#if defined(linux) || defined(sunV3)
	cp = (u_char *)&(((struct sockaddr_in *) &(ifnet.if_addr))->sin_addr.s_addr);
#else
	cp = (u_char *)&(((struct sockaddr_in *) &(in_ifaddr.ia_addr))->sin_addr.s_addr);
#endif

	op = current + 10;
	*op++ = *cp++;
	*op++ = *cp++;
	*op++ = *cp++;
	*op++ = *cp++;
	if (exact){
	    if (snmp_oid_compare(current, 14, name, *length) == 0){
		memcpy( (char *)lowest,(char *)current, 14 * sizeof(oid));
		lowinterface = interface;
#if defined(linux) || defined(sunV3)
		lowin_ifnet = ifnet;
#else
		lowin_ifaddr = in_ifaddr;
#endif
		break;	/* no need to search further */
	    }
	} else {
	    if ((snmp_oid_compare(current, 14, name, *length) > 0) &&
		 (!lowinterface || (snmp_oid_compare(current, 14, lowest, 14) < 0))){
		/*
		 * if new one is greater than input and closer to input than
		 * previous lowest, save this one as the "next" one.
		 */
		lowinterface = interface;
#if defined(linux) || defined(sunV3)
		lowin_ifnet = ifnet;
#else
		lowin_ifaddr = in_ifaddr;
#endif
		memcpy( (char *)lowest,(char *)current, 14 * sizeof(oid));
	    }
	}
    }

    if (!lowinterface) return(NULL);
    memcpy( (char *)name,(char *)lowest, 14 * sizeof(oid));
    *length = 14;
    *write_method = 0;
    *var_len = sizeof(long_return);
    switch(vp->magic){
	case IPADADDR:
#if defined(linux) || defined(sunV3)
            return(u_char *) &((struct sockaddr_in *) &lowin_ifnet.if_addr)->sin_addr.s_addr;
#else
	    return(u_char *) &((struct sockaddr_in *) &lowin_ifaddr.ia_addr)->sin_addr.s_addr;
#endif
	case IPADIFINDEX:
	    long_return = lowinterface;
	    return(u_char *) &long_return;
	case IPADNETMASK:
#ifndef sunV3
#ifdef linux
            return (u_char *)&((struct sockaddr_in *)&lowin_ifnet.ia_subnetmask)->sin_addr.s_addr;
#else
	    long_return = lowin_ifaddr.ia_subnetmask;
	    return(u_char *) &long_return;
#endif
#endif
	case IPADBCASTADDR:
	    
#if defined(linux) || defined(sunV3)
	    long_return = ntohl(((struct sockaddr_in *) &lowin_ifnet.ifu_broadaddr)->sin_addr.s_addr) & 1;
#else
	    long_return = ntohl(((struct sockaddr_in *) &lowin_ifaddr.ia_broadaddr)->sin_addr.s_addr) & 1;
#endif
	    return(u_char *) &long_return;	   
	case IPADREASMMAX:
#if NO_DUMMY_VALUES
	    return NULL;
#endif
	    long_return = -1;
	    return(u_char *) &long_return;
	default:
	    DEBUGMSGTL(("snmpd", "unknown sub-id %d in var_ipAddrEntry\n", vp->magic));
    }
    return NULL;
}

#ifdef freebsd2
static struct in_ifaddr *in_ifaddraddr;

static void
Address_Scan_Init (void)
{
    auto_nlist(IFADDR_SYMBOL, (char *)&in_ifaddraddr, sizeof(in_ifaddraddr));
}

/* NB: Index is the number of the corresponding interface, not of the address */
static int Address_Scan_Next(Index, Retin_ifaddr)
short *Index;
struct in_ifaddr *Retin_ifaddr;
{
      struct in_ifaddr in_ifaddr;
      struct ifnet ifnet,*ifnetaddr;  /* NOTA: same name as another one */
      short iindex=1;

      while (in_ifaddraddr) {
          /*
           *      Get the "in_ifaddr" structure
           */
          klookup((unsigned long)in_ifaddraddr, (char *)&in_ifaddr, sizeof in_ifaddr);
          in_ifaddraddr = in_ifaddr.ia_next;

          if (Retin_ifaddr)
              *Retin_ifaddr = in_ifaddr;

	  /*
	   * Now, more difficult, find the index of the interface to which
	   * this address belongs
	   */

	  auto_nlist(IFNET_SYMBOL, (char *)&ifnetaddr, sizeof(ifnetaddr));
	  while (ifnetaddr && ifnetaddr != in_ifaddr.ia_ifp) {
	      klookup((unsigned long)ifnetaddr, (char *)&ifnet, sizeof ifnet);
	      ifnetaddr = ifnet.if_next;
	      iindex++;
	  }

	  /* XXX - might not find it? */

	  if (Index)
                  *Index = iindex;

          return(1);  /* DONE */
      }
      return(0);          /* EOF */
}

#endif /* freebsd2 */

#else /* solaris2 */


static int
IP_Cmp(void *addr, void *ep)
{
  if (((mib2_ipAddrEntry_t *)ep)->ipAdEntAddr ==
      *(IpAddress *)addr)
    return (0);
  else
    return (1);
}

u_char *
var_ipAddrEntry(struct variable *vp,
		oid *name,
		size_t *length,
		int exact,
		size_t *var_len,
		WriteMethod **write_method)
{
    /*
     * object identifier is of form:
     * 1.3.6.1.2.1.4.20.1.?.A.B.C.D,  where A.B.C.D is IP address.
     * IPADDR starts at offset 10.
     */
#define IP_ADDRNAME_LENGTH	14
#define IP_ADDRINDEX_OFF	10
    oid			    lowest[IP_ADDRNAME_LENGTH];
    oid			    current[IP_ADDRNAME_LENGTH], *op;
    u_char		    *cp;
    IpAddress		    NextAddr;
    mib2_ipAddrEntry_t	    entry, Lowentry;
    int			    Found = 0;
    req_e		    req_type;
    
    /* fill in object part of name for current (less sizeof instance part) */

    DEBUGMSGTL(("mibII/ip", "var_ipAddrEntry: "));
    DEBUGMSGOID(("mibII/ip", name, *length));
    DEBUGMSG(("mibII/ip"," %d\n", exact));

    memset (&Lowentry, 0, sizeof (Lowentry));
    memcpy( (char *)current,(char *)vp->name, (int)vp->namelen * sizeof(oid));
    if (*length == IP_ADDRNAME_LENGTH) /* Assume that the input name is the lowest */
      memcpy( (char *)lowest,(char *)name, IP_ADDRNAME_LENGTH * sizeof(oid));
    for (NextAddr = (u_long)-1, req_type = GET_FIRST;
	 ;
	 NextAddr = entry.ipAdEntAddr, req_type = GET_NEXT) {
      if (getMibstat(MIB_IP_ADDR, &entry, sizeof(mib2_ipAddrEntry_t),
		     req_type, &IP_Cmp, &NextAddr) != 0)
	break;
      COPY_IPADDR(cp, (u_char *)&entry.ipAdEntAddr, op, current + IP_ADDRINDEX_OFF);
      if (exact){
	if (snmp_oid_compare(current, IP_ADDRNAME_LENGTH, name, *length) == 0){
	  memcpy( (char *)lowest,(char *)current, IP_ADDRNAME_LENGTH * sizeof(oid));
	  Lowentry = entry;
	  Found++;
	  break;	/* no need to search further */
	}
      } else {
	if ((snmp_oid_compare(current, IP_ADDRNAME_LENGTH, name, *length) > 0) 
	    && (((NextAddr == (u_long)-1))
		|| (snmp_oid_compare(current, IP_ADDRNAME_LENGTH, lowest, IP_ADDRNAME_LENGTH) < 0)
		|| (snmp_oid_compare(name, *length, lowest, IP_ADDRNAME_LENGTH) == 0))){
	  /*
	   * if new one is greater than input and closer to input than
	   * previous lowest, and is not equal to it, save this one as the "next" one.
	   */
	  Lowentry = entry;
	  Found++;
	  memcpy( (char *)lowest,(char *)current, IP_ADDRNAME_LENGTH * sizeof(oid));
	}
      }
    }
    DEBUGMSGTL(("mibII/ip", "... Found = %d\n", Found));
    if (Found == 0)
      return(NULL);
    memcpy( (char *)name,(char *)lowest, IP_ADDRNAME_LENGTH * sizeof(oid));
    *length = IP_ADDRNAME_LENGTH;
    *write_method = 0;
    *var_len = sizeof(long_return);
    switch(vp->magic){
	case IPADADDR:
      	    long_return = Lowentry.ipAdEntAddr;
	    return(u_char *) &long_return;
	case IPADIFINDEX:
	    long_return = Interface_Index_By_Name(Lowentry.ipAdEntIfIndex.o_bytes,
						  Lowentry.ipAdEntIfIndex.o_length);
	    return(u_char *) &long_return;
	case IPADNETMASK:
	    long_return = Lowentry.ipAdEntNetMask;
	    return(u_char *) &long_return;
	case IPADBCASTADDR:
	    long_return = Lowentry.ipAdEntBcastAddr;
	    return(u_char *) &long_return;	   
	default:
	    DEBUGMSGTL(("snmpd", "unknown sub-id %d in var_ipAddrEntry\n", vp->magic));
    }
    return NULL;
}

#endif /* solaris2 */



	/*********************
	 *
	 *  Internal implementation functions
	 *
	 *********************/


#else /* CAN_USE_SYSCTL && IPCTL_STATS */

/*
 * Ideally, this would be combined with the code in interfaces.c.
 * Even separate, it's still better than what went before.
 */
struct iflist {
	int flags;
	int index;
	struct in_addr addr;
	struct in_addr mask;
	struct in_addr bcast;
};
static struct iflist *ifs;
static int nifs;

static void
get_iflist(void)
{
	int naddrs, bit;
	static int mib[6] 
		= { CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_IFLIST, 0 };
	char *cp, *ifbuf;
	size_t len;
	struct rt_msghdr *rtm;
	struct if_msghdr *ifm;
	struct ifa_msghdr *ifam;
	struct sockaddr *sa;
	int flags;

	naddrs = 0;
	if (ifs)
		free(ifs);
	ifs = 0;
	nifs = 0;
	len = 0;
	if (sysctl(mib, 6, 0, &len, 0, 0) < 0)
		return;

	ifbuf = malloc(len);
	if (ifbuf == 0)
		return;
	if (sysctl(mib, 6, ifbuf, &len, 0, 0) < 0) {
		syslog(LOG_WARNING, "sysctl net-route-iflist: %m");
		free(ifbuf);
		return;
	}

loop:
	cp = ifbuf;
	while (cp < &ifbuf[len]) {
		int gotaddr;

		gotaddr = 0;
		rtm = (struct rt_msghdr *)cp;
		if (rtm->rtm_version != RTM_VERSION 
		    || rtm->rtm_type != RTM_IFINFO) {
			free(ifs);
			ifs = 0;
			nifs = 0;
			free(ifbuf);
			return;
		}
		ifm = (struct if_msghdr *)rtm;
		flags = ifm->ifm_flags;
		cp += ifm->ifm_msglen;
		rtm = (struct rt_msghdr *)cp;
		while (cp < &ifbuf[len] && rtm->rtm_type == RTM_NEWADDR) {
			ifam = (struct ifa_msghdr *)rtm;
			cp += sizeof(*ifam);
/* from route.c */
#define ROUND(a) \
        ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
			for (bit = 1; bit && cp < &ifbuf[len]; bit <<= 1) {
				if (!(ifam->ifam_addrs & bit))
					continue;
				sa = (struct sockaddr *)cp;
				cp += ROUND(sa->sa_len);
				
				/*
				 * Netmasks are returned as bit
				 * strings of type AF_UNSPEC.  The
				 * others are pretty ok.
				 */
				if (bit == RTA_IFA) {
#define satosin(sa) ((struct sockaddr_in *)(sa))
					if (ifs) {
						ifs[naddrs].addr 
							=satosin(sa)->sin_addr;
						ifs[naddrs].index
							= ifam->ifam_index;
						ifs[naddrs].flags = flags;
					}
					gotaddr = 1;
				} else if (bit == RTA_NETMASK) {
					if (ifs)
						ifs[naddrs].mask 
							=satosin(sa)->sin_addr;
				} else if (bit == RTA_BRD) {
					if (ifs)
						ifs[naddrs].bcast 
							=satosin(sa)->sin_addr;
				}
			}
			if (gotaddr)
				naddrs++;
			cp = (char *)rtm + rtm->rtm_msglen;
			rtm = (struct rt_msghdr *)cp;
		}
	}
	if (ifs) {
		nifs = naddrs;
		free(ifbuf);
		return;
	}
	ifs = malloc(naddrs * sizeof(*ifs));
	if (ifs == 0) {
		free(ifbuf);
		return;
	}
	naddrs = 0;
	goto loop;
}

u_char *
var_ipAddrEntry(struct variable *vp,
		oid *name,
		size_t *length,
		int exact,
		size_t *var_len,
		WriteMethod **write_method)
{
	/*
	 * object identifier is of form:
	 * 1.3.6.1.2.1.4.20.1.?.A.B.C.D,  where A.B.C.D is IP address.
	 * IPADDR starts at offset 10.
	 */
	oid lowest[14];
	oid current[14], *op;
	u_char *cp;
	int lowinterface = -1;
	int i, interface;

	/* fill in object part of name for current (less sizeof instance part) */
	memcpy(current, vp->name, (int)vp->namelen * sizeof(oid));

	/*
	 * Get interface table from kernel.
	 */
	get_iflist();

	for (i = 0; i < nifs; i++) {
		op = &current[10];
		cp = (u_char *)&ifs[i].addr;
		*op++ = *cp++;
		*op++ = *cp++;
		*op++ = *cp++;
		*op++ = *cp++;
		if (exact) {
			if (snmp_oid_compare(current, 14, name, *length) == 0) {
				memcpy(lowest, current, 14 * sizeof(oid));
				lowinterface = i;
				break;	/* no need to search further */
			}
		} else {
			if ((snmp_oid_compare(current, 14, name, *length) > 0) &&
			    (lowinterface < 0 
			     || (snmp_oid_compare(current, 14, lowest, 14) < 0))) {
				/*
				 * if new one is greater than input
				 * and closer to input than previous
				 * lowest, save this one as the "next"
				 * one.  
				 */
				lowinterface = i;
				memcpy(lowest, current, 14 * sizeof(oid));
			}
		}
	}

	if (lowinterface < 0)
		return NULL;
	i = lowinterface;
	memcpy(name, lowest, 14 * sizeof(oid));
	*length = 14;
	*write_method = 0;
	*var_len = sizeof(long_return);
	switch (vp->magic) {
	case IPADADDR:
		long_return = ifs[i].addr.s_addr;
		return (u_char *)&long_return;

	case IPADIFINDEX:
		long_return = ifs[i].index;
		return (u_char *)&long_return;

	case IPADNETMASK:
		long_return = ifs[i].mask.s_addr;
		return (u_char *)&long_return;

	case IPADBCASTADDR:
		long_return = ntohl(ifs[i].bcast.s_addr) & 1;
		return (u_char *)&long_return;	   

	case IPADREASMMAX:
#if NO_DUMMY_VALUES
		return NULL;
#endif
		long_return = -1;
		return (u_char *)&long_return;

	default:
		DEBUGMSGTL(("snmpd", "unknown sub-id %d in var_ipAddrEntry\n", vp->magic));
	}
	return NULL;
}

#endif /* CAN_USE_SYSCTL && IPCTL_STATS */

#else /* WIN32 */
#include <iphlpapi.h>
u_char *
var_ipAddrEntry(struct variable *vp,
		oid *name,
		size_t *length,
		int exact,
		size_t *var_len,
		WriteMethod **write_method)
{
	/*
	 * object identifier is of form:
	 * 1.3.6.1.2.1.4.20.1.?.A.B.C.D,  where A.B.C.D is IP address.
	 * IPADDR starts at offset 10.
	 */
	oid lowest[14];
	oid current[14], *op;
	u_char *cp;
	int lowinterface = -1;
	int i;
  PMIB_IPADDRTABLE pIpAddrTable = NULL;
    DWORD status = NO_ERROR;
    DWORD statusRetry = NO_ERROR;
    DWORD dwActualSize = 0;

	/* fill in object part of name for current (less sizeof instance part) */
	memcpy(current, vp->name, (int)vp->namelen * sizeof(oid));

	/*
	 * Get interface table from kernel.
	 */
	 status = GetIpAddrTable(pIpAddrTable, &dwActualSize, TRUE);
   if (status == ERROR_INSUFFICIENT_BUFFER)
   {
     pIpAddrTable = (PMIB_IPADDRTABLE) malloc(dwActualSize);
     if(pIpAddrTable != NULL){       
       statusRetry = GetIpAddrTable(pIpAddrTable, &dwActualSize, TRUE);
     }
   }

	if(statusRetry == NO_ERROR || status == NO_ERROR)
    {
      
        for (i = 0; i < (int)pIpAddrTable->dwNumEntries; ++i)
        {
		op = &current[10];
		cp = (u_char *)&pIpAddrTable->table[i].dwAddr;
		*op++ = *cp++;
		*op++ = *cp++;
		*op++ = *cp++;
		*op++ = *cp++;
		if (exact) {
			if (snmp_oid_compare(current, 14, name, *length) == 0) {
				memcpy(lowest, current, 14 * sizeof(oid));
				lowinterface = i;
				break;	/* no need to search further */
			}
		} else {
			if (snmp_oid_compare(current, 14, name, *length) > 0) {

				lowinterface = i;
				memcpy(lowest, current, 14 * sizeof(oid));
        break; /* Since the table is sorted, no need to search further  */
			}
		}
	}
  }

  if (lowinterface < 0){
    free(pIpAddrTable);
		return NULL;
  }
	i = lowinterface;
	memcpy(name, lowest, 14 * sizeof(oid));
	*length = 14;
	*write_method = 0;
	*var_len = sizeof(long_return);
	switch (vp->magic) {
	case IPADADDR:
		long_return = pIpAddrTable->table[i].dwAddr;
		return (u_char *)&long_return;

	case IPADIFINDEX:
		long_return = pIpAddrTable->table[i].dwIndex;
    free(pIpAddrTable);
		return (u_char *)&long_return;

	case IPADNETMASK:
		long_return = pIpAddrTable->table[i].dwMask;
    free(pIpAddrTable);
		return (u_char *)&long_return;

	case IPADBCASTADDR:
		long_return = pIpAddrTable->table[i].dwBCastAddr;
    free(pIpAddrTable);
		return (u_char *)&long_return;	   

	case IPADREASMMAX:
		long_return = pIpAddrTable->table[i].dwReasmSize;
    free(pIpAddrTable);
		return (u_char *)&long_return;

	default:
		DEBUGMSGTL(("snmpd", "unknown sub-id %d in var_ipAddrEntry\n", vp->magic));
	}
	return NULL;
}
#endif /* WIN32 */
