/* $Header: nip.c,v 1.1 87/09/22 16:03:01 mar Exp $
 *
 * Network Information Protocol module of netconfig
 */

#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <sys/ioctl.h>
#include <net/nit.h>
#include <net/nip.h>
#include "netconfig.h"


static struct nipinfo ni;

unsigned short cksum();
extern int debug;


struct nipinfo *getnip(s, pa)
int	s;
u_char	*pa;
{
    int i, rfds, xfds;
    struct sockaddr sa;
    struct nit_ioc nic;
    struct nip_pkt pkt;
    struct nit_hdr *nh;
    struct ether_header *eh;
    struct nip_pkt *np;
    unsigned char buf[sizeof(*nh) + sizeof(*eh) + sizeof(*np)];
    struct timeval starttime, tmout;
    struct timezone tz;

    nic.nioc_bufspace = BUFSIZE;
    nic.nioc_chunksize = sizeof(struct nit_hdr) + sizeof(struct nip_pkt);
    nic.nioc_typetomatch = ETHERTYPE_NIP;
    nic.nioc_snaplen = sizeof(struct nip_pkt) + sizeof(struct ether_header);
    nic.nioc_flags = 0;
    if (ioctl(s, SIOCSNIT, &nic) < 0) {
	perror("ioctl");
	return(0);
    }

    /* Compose and send request */
    bzero(&pkt, sizeof(pkt));
    bcopy(pa, &pkt, sizeof(struct ether_addr));
    pkt.np_opcode = htons(NIP_REQUEST);
    pkt.np_version = htons(NIP_VERSION);
    pkt.np_checksum = ~cksum(&pkt, sizeof(pkt));
    sa.sa_family = AF_UNSPEC;
    eh = (struct ether_header *) &(sa.sa_data[0]);
    for (i = 0; i < 6; i++)
      eh->ether_dhost[i] = -1;
    eh->ether_type = ETHERTYPE_NIP;
    if (sendto(s, &pkt, sizeof(pkt), 0, &sa, sizeof(sa)) < 0) {
	perror("sendto");
	return(0);
    }

    /* wait for replies */
    gettimeofday(&starttime, &tz);
    tmout.tv_sec = WAITTIME;
    rfds = xfds = (1 << s);
    while (1) {
	gettimeofday(&tmout, &tz);
	if (tmout.tv_sec >= starttime.tv_sec + WAITTIME)
	  break;
	tmout.tv_sec = starttime.tv_sec + WAITTIME - tmout.tv_sec;
	if (select(s+1, &rfds, NULL, &xfds, &tmout) == 0)
	  break;

	/* get & validate packet */
	i = read(s, buf, sizeof(buf));
	nh = (struct nit_hdr *) &buf[0];
	eh = (struct ether_header *) &buf[sizeof(*nh)];
	np = (struct nip_pkt *) &buf[sizeof(*nh) + sizeof(*eh)];


	if ((nh->nh_state != NIT_CATCH) ||
	    (i < sizeof(buf)) ||
	    (nh->nh_datalen < sizeof(struct nip_pkt)) ||
	    (np->np_version != htons(NIP_VERSION)) ||
	    (np->np_opcode != htons(NIP_RESPONSE)) ||
	    (cksum((unsigned short *) np, sizeof(struct nip_pkt)) != CKSUMOK))
	  continue;

/*	if (nh->nh_state != NIT_CATCH) {
	    printf("bad nit state\n");
	    continue;
	}
	if (nh->nh_datalen < sizeof(struct nip_pkt)) {
	    printf("bad data length\n");
	    continue;
	}
	if (np->np_version != htons(NIP_VERSION)) {
	    printf("bad version\n");
	    continue;
	}
	if (np->np_opcode != htons(NIP_RESPONSE)) {
	    printf("bad opcode\n");
	    continue;
	}
	if (cksum(np, sizeof(struct nip_pkt)) != CKSUMOK) {
	    printf("bad checksum %x (%x)\n",
		   cksum(np, sizeof(struct nip_pkt)), np->np_checksum);
	    continue;
	}
*/
	/* copy out data */
	ni.ni_netaddr	= np->np_netaddress;
	ni.ni_netmask	= np->np_netmask;
	ni.ni_broadcast	= np->np_broadcast;
	ni.ni_gateway	= np->np_gateway;
	ni.ni_flags    |= NI_NETADDR|NI_NETMASK|NI_BROADCAST|NI_GATEWAY;
	if (np->np_lowest) {
	    ni.ni_lowest = np->np_lowest;
	    ni.ni_flags |= NI_LOWEST;
	}
	if (np->np_highest) {
	    ni.ni_highest = np->np_highest;
	    ni.ni_flags |= NI_HIGHEST;
	}
	if (np->np_recommend) {
	    ni.ni_recommend	= np->np_recommend;
	    ni.ni_flags |= NI_RECOMMEND;
	}
	return(&ni);
    }
    return(0);
}



unsigned short cksum(start, length)
unsigned short *start;
int	length;
{
    unsigned long acc = 0;

    length /= 2;
    while (length--)
      acc += *start++;
    acc = (acc & 0xffff) + (acc >> 16);
    acc = (acc & 0xffff) + (acc >> 16);
    return((unsigned short) acc);
}


enablenip(s, ni)
int	s;
struct nipinfo *ni;
{
    struct nipreq nr;

    nr.nip_state = 1;
    nr.nip_data.np_netaddress = ni->ni_netaddr;
    nr.nip_data.np_netmask = ni->ni_netmask;
    nr.nip_data.np_broadcast = ni->ni_broadcast;
    nr.nip_data.np_lowest = ni->ni_lowest;
    nr.nip_data.np_highest = ni->ni_highest;
    nr.nip_data.np_recommend = 0L;
    nr.nip_data.np_gateway = ni->ni_gateway;
    if (ioctl(s, SIOCSNIP, &nr) < 0)
      perror("ioctl");
    if (debug)
      printf("in-kernel NIP server now enabled\n");
}
