/*
 * Center for Information Technology Integration
 *           The University of Michigan
 *                    Ann Arbor
 *
 * Dedicated to the public domain.
 * Send questions to info@citi.umich.edu
 *
 * BOOTP is documented in RFC 951 and RFC 1048
 * Delinted, ANSIfied and reformatted - 5/30/91 P. Karn
 */


#include "global.h"
#ifdef BOOTPCLIENT
#include "commands.h"
#ifndef MSDOS
#include <time.h>
#endif
#include "mbuf.h"
#include "netuser.h"
#include "udp.h"
#include "domain.h"
#include "bootp.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: bootp.c,v 1.17 1997/01/19 21:13:05 root Exp $";
#endif

static int bootp_rx (struct iface *ifp, struct mbuf *bp);
static void ntoh_bootp (struct mbuf **bpp, struct bootp *bootpp);

#if 0
static int mask2width (int32 mask);
#endif

#define BOOTP_TIMEOUT	30	/* Time limit for booting       */
#define BOOTP_RETRANS	5	/* The inteval between sendings */

#ifdef	BOOTP
int WantBootp = 0;
#endif

static int SilentStartup = 0;



int
dobootp (int argc, char **argv, void *p OPTIONAL)
{
struct iface *ifp = NULLIF;
struct socket lsock, fsock;
struct mbuf *bp;
struct udp_cb *bootp_cb;
register unsigned char *cp;
time_t now,		/* The current time (seconds)   */
       starttime,	/* The start time of sending BOOTP */
       lastsendtime;	/* The last time of sending BOOTP  */
int i;

	if (argc < 2)		/* default to the first interface */
		ifp = Ifaces;
	else {
		for (i = 1; i != argc; ++i) {

			if ((ifp = if_lookup (argv[i])) != NULLIF)
				continue;
			else if (strncmp (argv[i], "silent", strlen (argv[i])) == 0)
				SilentStartup = 1;
			else if (strncmp (argv[i], "noisy", strlen (argv[i])) == 0)
				SilentStartup = 0;
			else {
				tputs ("bootp [net_name] [silent] [noisy]\n");
				return 1;
			}
		}
	}

	if (ifp == NULLIF)
		return 0;

	lsock.address = ifp->addr;
	lsock.port = IPPORT_BOOTPC;

	if ((bootp_cb = open_udp (&lsock, NULLVFP ((struct iface *, struct udp_cb *, int16)))) == NULLUDP)
		return 0;

	fsock.address = ifp->broadcast;
	fsock.port = IPPORT_BOOTPS;

	/* Get boot starting time */
	(void) time (&starttime);
	lastsendtime = 0;

	/* Send the bootp request packet until a response is received or time
	   out */
	for ( ; ; ) {

		/* Allow bootp packets should be passed through iproute. */
		WantBootp = 1;

		/* Get the current time */
		(void) time (&now);

		/* Stop, if time out */
		if (now - starttime >= BOOTP_TIMEOUT) {
			tputs ("bootp: timed out, values not set\n");
			break;
		}
		/* Don't flood the network, send in intervals */
		if (now - lastsendtime > BOOTP_RETRANS) {
			if (!SilentStartup)
				tputs ("Requesting...\n");

			/* Allocate BOOTP packet and fill it in */
			if ((bp = alloc_mbuf (sizeof (struct bootp))) == NULLBUF)
				      break;

			cp = bp->data;	/* names per the RFC: */
			*cp++ = BOOTREQUEST;			/* op */
			*cp++ = uchar(ifp->iftype->type);	/* htype */
			*cp++ = uchar(ifp->iftype->hwalen);	/* hlen */
			*cp++ = 0;				/* hops */
			cp = put32 (cp, (uint32) now);		/* xid */
			cp = put16 (cp, (int16)(now - starttime)); /* secs */
			cp = put16 (cp, 0);			/* unused */
			cp = put32 (cp, ifp->addr);		/* ciaddr */
			cp = put32 (cp, 0L);			/* yiaddr */
			cp = put32 (cp, 0L);			/* siaddr */
			cp = put32 (cp, 0L);			/* giaddr */
			memcpy (cp, ifp->hwaddr, (size_t) ifp->iftype->hwalen);
			cp += 16;				/* chaddr */
			memset (cp, 0, 64);			/* sname */
			cp += 64;
			memset (cp, 0, 128);			/* file */
			cp += 128;
			memset (cp, 0, 64);			/* vend */
			cp += 64;
			bp->cnt = (int16)(cp - bp->data);
			/* assert(bp->cnt == BOOTP_LEN) */

			/* Send out one BOOTP Request packet as a broadcast */
			(void) send_udp (&lsock, &fsock, 0, 0, bp, bp->cnt, 0, 0);

			lastsendtime = now;
		}
		/* Give other tasks a chance to run. */
		kwait (NULL);

		/* Test for and process any replies */
		if (recv_udp (bootp_cb, &fsock, &bp) > -1) {
			if (bootp_rx (ifp, bp))
				break;
		} else if (Net_error != WOULDBLK) {
			tprintf ("bootp: Net_error %d, no values set\n", Net_error);
			break;
		}
	}

	WantBootp = 0;
	(void) del_udp (bootp_cb);
	return 0;
}



/* Process BOOTP input received from 'interface'. */
static int
bootp_rx (struct iface *ifp, struct mbuf *bp)
{
int ch;
int count;
uint32 gateway = 0;
uint32 nameserver = 0;
uint32 broadcast, netmask;
struct route *rp;
struct bootp reply;
unsigned char *cp;

	if (len_p (bp) != sizeof (struct bootp)) {
		free_p (bp);
		return 0;
	}
	ntoh_bootp (&bp, &reply);
	free_p (bp);

	if (reply.op != BOOTREPLY)
		return 0;

	if (!SilentStartup)
		tprintf ("Network %s configured:\n", ifp->name);

	if (ifp->addr == 0) {
		Ip_addr = reply.yiaddr.s_addr;		/* yiaddr */
		ifp->addr = reply.yiaddr.s_addr;	/* yiaddr */
		if (!SilentStartup)
			tprintf ("     IP address: %s\n", inet_ntoa (ifp->addr));
	}
	/* now process the vendor-specific block, check for cookie first. */
	cp = (unsigned char *) reply.vend;
	if (get32 (reply.vend) != 0x63825363L) {
		tcmdprintf ("bootp: Invalid magic cookie.\n");
		return (0);
	}
	cp += 4;
	while (((ch = *cp) != BOOTP_END) && (++cp < (unsigned char *) (reply.vend + 64)))
		switch (ch) {
			case BOOTP_PAD:	/* They're just padding */
				continue;
			case BOOTP_SUBNET:	/* fixed length, 4 octets */
				cp++;	/* moved past length */

				/* Set the netmask */
				/* Remove old entry if it exists */
				netmask = get32 ((char *) cp);
				cp += 4;	/*lint !e662 */

				rp = rt_blookup (ifp->addr & ifp->netmask, (unsigned) mask2width (ifp->netmask));
				if (rp != NULLROUTE)
					(void) rt_drop (rp->target, rp->bits);
				ifp->netmask = netmask;
				(void) rt_add (ifp->addr, (unsigned) mask2width (ifp->netmask), 0L, ifp, 0L, 0L, 0);

				if (!SilentStartup)
					tprintf ("     Subnet mask: %s\n", inet_ntoa (netmask));

				/* Set the broadcast */
				broadcast = ifp->addr | ~(ifp->netmask);
				rp = rt_blookup (ifp->broadcast, 32);
				if (rp != NULLROUTE && rp->iface == ifp)
					(void) rt_drop (ifp->broadcast, 32);
				ifp->broadcast = broadcast;
				(void) rt_add (ifp->broadcast, 32, 0L, ifp, 1L, 0L, 1);

				if (!SilentStartup)
					tprintf ("     Broadcast: %s\n", inet_ntoa (broadcast));

				break;
			case BOOTP_HOSTNAME:
				count = (int) *cp;
				cp++;

				if (Hostname != NULLCHAR)
					free (Hostname);
				Hostname = mallocw ((unsigned) count);
				strncpy (Hostname, (char *) cp, (size_t) count);
				cp += count;

				if (!SilentStartup)
					tprintf ("     Hostname: %s\n", Hostname);
				break;
			case BOOTP_DNS:
				count = (int) *cp;
				cp++;

				while (count) {
					nameserver = get32 ((char *) cp);
					(void) add_nameserver (nameserver, 0);
					if (!SilentStartup)
						tprintf ("     Nameserver: %s\n", inet_ntoa (nameserver));
					cp += 4;	/*lint !e662 */
					count -= 4;
				}
				break;
			case BOOTP_GATEWAY:
				count = (int) *cp;
				cp++;

				gateway = get32 ((char *) cp);

				/* Add the gateway as the default */
				(void) rt_add (0, 0, gateway, ifp, 1, 0, 0);

				if (!SilentStartup)
					tprintf ("     Default gateway: %s\n", inet_ntoa (gateway));
				cp += count;
				break;
			default:	/* variable field we don't know about */
				count = (int) *cp;
				cp++;

				cp += count;
				break;
		}

	(void) rt_add (ifp->addr, (unsigned) mask2width (ifp->netmask), 0L, ifp, 1, 0, 0);	/*lint !e661 !e662 */

	return (1);
}



static void
ntoh_bootp (struct mbuf **bpp, struct bootp *bootpp)
{
	bootpp->op = (char) pullchar (bpp);		/* op */
	bootpp->htype = (char) pullchar (bpp);		/* htype */
	bootpp->hlen = (char) pullchar (bpp);		/* hlen */
	bootpp->hops = (char) pullchar (bpp);		/* hops */
	bootpp->xid = (long) pull32 (bpp);		/* xid */
	bootpp->secs = pull16 (bpp);			/* secs */
	bootpp->unused = pull16 (bpp);			/* unused */
	bootpp->ciaddr.s_addr = pull32 (bpp);		/* ciaddr */
	bootpp->yiaddr.s_addr = pull32 (bpp);		/* ciaddr */
	bootpp->siaddr.s_addr = pull32 (bpp);		/* siaddr */
	bootpp->giaddr.s_addr = pull32 (bpp);		/* giaddr */
	(void) pullup (bpp, (unsigned char *) bootpp->chaddr, 16);	/* chaddr */
	(void) pullup (bpp, (unsigned char *) bootpp->sname, 64);	/* sname */
	(void) pullup (bpp, (unsigned char *) bootpp->file, 128);	/* file name */
	(void) pullup (bpp, (unsigned char *) bootpp->vend, 64);	/* vendor */
}



#ifdef	BOOTP

int
bootp_validPacket (struct ip *ip, struct mbuf **bpp)
{
struct udp udp;
struct pseudo_header ph;
int status;


	/* Must be a udp packet */
	if (ip->protocol != UDP_PTCL)
		return 0;

	/* Invalid if packet is not the right size */
	if (len_p (*bpp) != (sizeof (struct udp) + sizeof (struct bootp)))
		return 0;

	/* Invalid if not a udp bootp packet */
	ntohudp (&udp, bpp);

	status = (udp.dest == IPPORT_BOOTPC) ? 1 : 0;

	/* Restore packet, data hasn't changed */
	/* Create pseudo-header and verify checksum */
	ph.source = ip->source;
	ph.dest = ip->dest;
	ph.protocol = ip->protocol;
	ph.length = ip->length - IPLEN - ip->optlen;

	*bpp = htonudp (&udp, *bpp, &ph);

	return status;
}

#endif



#if 0				/* no longer needed, as it is in iface.c */
/* Given a network mask, return the number of contiguous 1-bits starting
 * from the most significant bit.
 */
static int
mask2width (int32 mask)
{
int width, i;

	width = 0;
	for (i = 31; i >= 0; i--) {
		if (!(mask & (1L << i)))
			break;
		width++;
	}
	return width;
}

#endif

#endif	/* BOOTPCLIENT */
