/*
 * 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
 */


/*
 * BOOTP (bootstrap protocol) server daemon.
 *
 */


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <time.h>

#include "global.h"
#include "config.h"
#include "bootp.h"
#include "cmdparse.h"
#include "iface.h"
#include "mbuf.h"
#include "proc.h"
#include "socket.h"
#include "bootpd.h"
#include "udp.h"
#include "ip.h"
#include "arp.h"
#include "netuser.h"

void bootpd __ARGS((struct iface *iface, struct udp_cb *sock, int cnt)); 

struct udp_cb *Bootpd_cb = NULLUDP;

char homedir[64] = "";      		/* bootfile homedirectory */
char defaultboot[64] = "";  		/* default file to boot */

struct host hosts[MHOSTS+1];
int Nhosts = 0;  	 /* current number of hosts */

int32 bp_DefaultDomainNS[BP_MAXDNS] =  {0};

struct bootpd_stat Bootpd_stat;

char *ArpNames[] = {
	"Netrom",
	"Ethernet",
	"AX25",
	"Pronet",
	"Chaos",
	"IEEE 802",
	"ArcNet",
	"AppleTalk" 
};


/* Routine declarations */
static int bootpd_recv __ARGS((struct udp_cb *sock,struct bootp *bootp));
static void sendreply __ARGS((struct bootp *bp,struct iface *iface));
static void vend_fill __ARGS((char *vend,struct iface *iface,struct host *hp));
static void vend_print __ARGS((char *vend));
static void bootpd_request __ARGS((struct bootp *rq,struct iface *iface));

/* The bootp server. */
void 
bootpd(iface,sock,cnt)
struct iface *iface;
struct udp_cb *sock;
int cnt;
{
	struct bootp bp_packet;

	while(bootpd_recv(sock, &bp_packet) != -1) {

       		if(readtab() == -1) /* maybe re-read bootptab */
			return;

		switch(uchar(bp_packet.op)) {
	    	case BOOTREQUEST:
			bootpd_request(&bp_packet, iface); 
			break;
		case BOOTREPLY: 
			/* Replies are not forwarded, left to the gateway */ 
			break;
		}

	}

}

/* A packet has been received, read it into a bootp structure. */
static int
bootpd_recv(sock, bootp)
struct udp_cb *sock;
struct bootp *bootp;
{
	struct socket fsock;
	struct mbuf *bp;
	int len;

	/* increment the rcvd cnt */
	Bootpd_stat.rcvd++;

	/* receive the packet */
	len = recv_udp(sock,&fsock,&bp);
	if(len == -1)
		return -1;


	/* check length of packet */
	if(len < sizeof(struct bootp)) {
		Bootpd_stat.bad_size++;
		bootp->op = 0;
		free_p(bp);
		return -1;
	}
		
	/* parse the packet */
	pullup (&bp, (char *)bootp, sizeof (struct bootp));
	free_p(bp);	

	if(bootp->op != BOOTREPLY  &&  bootp->op != BOOTREQUEST) {
		Bootpd_stat.bad_op++;
		bootp->op = 0;
		return -1;
	}

	bootp->ciaddr.s_addr = (unsigned long) get32 ((char *)&(bootp->ciaddr));
	bootp->giaddr.s_addr = (unsigned long) get32 ((char *)&(bootp->giaddr));
	return 0;
};


/*
 * Process BOOTREQUEST packet.
 *
 * (Note, this version of the bootp.c server never forwards 
 * the request to another server.  In our environment the 
 * stand-alone gateways perform that function.)
 *
 * (Also this version does not interpret the hostname field of
 * the request packet;  it COULD do a name->address lookup and
 * forward the request there.)
 */

static void
bootpd_request (rq, iface)
struct bootp *rq;
struct iface *iface;
{

	struct bootp *rp;
	char path[64], file[64];
	struct host *hp;
	int n;
	time_t tloc;
	int32 ipaddr;
	struct arp_type *at;

	time(&tloc);
	bp_log ("\nBootpd request packet received %s", ctime(&tloc));
	/* Forwarding not done here. */
	if(rq->giaddr.s_addr)  {
		bp_log ("     Dropped, giaddr specifies to be forwarded;\n");
		return;
	}

	/* Is a specific host requested? */
	if((strlen(rq->sname) != 0) && (strcmp (Hostname, rq->sname) != 0)) {
		bp_log ("     Dropped, sname specifies server '%s'\n", rq->sname);  
		return;
	}

	/* allocate the reply */
	rp = (struct bootp *) calloc (1, sizeof (struct bootp));

	/* copy for construction */
	memcpy (rp, rq, sizeof (struct bootp) - sizeof (rp->vend));

	rp->op = BOOTREPLY;

	hp = NULLHOST;


	/* If the client doesn't know it's ip address, find one. */
	if(rq->ciaddr.s_addr == 0) { 

		/*
		 * client doesn't know his IP address, 
		 * search by hardware address.
		 */
		at = &Arp_type[rq->htype];
		bp_log ("     Resolved by %s addr %s\n", ArpNames[rq->htype], 
			(*at->format)(bp_ascii, rq->chaddr));

		for(hp = &hosts[0], n = 0 ; n < Nhosts ; n++,hp++)
			if(((int16)rq->htype == hp->htype)
			   && (strncmp(rq->chaddr, hp->haddr, (size_t)rq->hlen) == 0))
				break;

		/* If the client wasn't found, assign an IP address */
		if(n == Nhosts) {

			hp = NULLHOST;
			if(da_assign(iface, rq->chaddr, &ipaddr) != 0) {
				free (rp);
				bp_log ("     No dynamic addresses available.\n");
				return;
			} else {		
				put32 ((char *) &(rp->yiaddr), ipaddr);
				bp_log ("     Dynamic address assigned: %s\n", 
					inet_ntoa (ipaddr));
			}
		} else {
			bp_log ("     Static address assigned: %s\n", 
				inet_ntoa ((int32)hp->iaddr.s_addr));
			put32 ((char *) &(rp->yiaddr), (int32)hp->iaddr.s_addr);
		}

	} else {
		/* search by IP address */
		bp_log ("     Resolve by IP addr %s\n", inet_ntoa ((int32)rq->ciaddr.s_addr));
		for(hp = &hosts[0], n = 0 ; n < Nhosts ; n++,hp++)
			if(rq->ciaddr.s_addr == hp->iaddr.s_addr)
				break;
		if(n == Nhosts) {
			hp = NULLHOST;
			bp_log ("     Host not found, default values used.\n");
		} else
			bp_log ("     Lookup successful.\n");
		put32 ((char *) &(rp->ciaddr), (int32)rq->ciaddr.s_addr);
	}

	put32 ((char *) &(rp->siaddr), iface->addr);

	/* Determine the bootp file */
	file[0] = 0;
	if(rq->file[0] == 0) { 		/* if client didn't specify file */
		/* Use the host record file, else the default file */
		if((hp == NULLHOST) || (hp->bootfile[0] == 0))
			strcpy(file, defaultboot);
		else
			strcpy(file, hp->bootfile);
	} else {
		/* use client specified file */
		strcpy(file, rq->file);
	}

	/* If a file is specified, specify the path to the bootp file */
	path[0] = 0;
	if((*homedir != 0) && (*file != 0)) {
		strcpy(path, homedir);
		strcat(path, "/");
	}

	if(file[0] == '/')     /* if absolute pathname */
		strcpy(path, file);
	else
		strcat(path, file);

	/* No files are provided here, just return a path. */
	strcpy(rp->file, path);

	/* Fill in the vendor information */
	vend_fill (rp->vend, iface, hp);

	sendreply (rp, iface);
	free (rp); 

};


/* Print the bootp structure. */
void
bootp_print_packet  (bp)
struct bootp *bp;
{
	bp_log ("Packet op code........................%d\n", bp->op);
	bp_log ("hardware address type.................%d\n", bp->htype);
	bp_log ("hardware address length...............%d\n", bp->hlen);
	bp_log ("client sets to zero...................%d\n", bp->hops);
	bp_log ("transaction ID........................%ld\n", bp->xid);
	bp_log ("seconds elapsed since client booted...%d\n", bp->secs);
	bp_log ("unused................................%d\n", bp->unused);
	bp_log ("Client IP address, if known...........%s\n",
			inet_ntoa ((int32)bp->ciaddr.s_addr));
	bp_log ("Server supplied IP address............%s\n", 
			inet_ntoa ((int32)bp->yiaddr.s_addr));
	bp_log ("Server IP address.....................%s\n", 
			inet_ntoa ((int32)bp->siaddr.s_addr));
	bp_log ("Gateway IP address....................%s\n", 
			inet_ntoa ((int32)bp->giaddr.s_addr));
	bp_log ("Client hardware address...............%x:%x:%x:%x:%x:%x\n",
		bp->chaddr[0], bp->chaddr[1], bp->chaddr[2],
		bp->chaddr[3], bp->chaddr[4], bp->chaddr[5]);
	bp_log ("Server host name......................'%s'\n", bp->sname);
	bp_log ("Boot file name........................'%s'\n", bp->file);
	
	vend_print (bp->vend);	
}



static void
vend_print (vend)
char *vend; 
{
	unsigned char ch;
	int size;
	char *start;
	int32 *ipaddr;
	int i;

	start = vend;

	bp_log ("Magic Cookie..........................x%02x%02x%02x%02x\n", 
		(int) vend[0], (int) vend[1], (int) vend[2], (int) vend[3]);
	vend = vend + 4;

	while(((ch = *vend++) != BOOTP_END) && (vend - start <= 64)) 
		switch(ch) {

		case BOOTP_PAD:	 /* They're just padding */
			continue;
		case BOOTP_SUBNET:      /* fixed length, 4 octets */
			size = (int) *vend++;
			ipaddr = (int32 *) vend;
			bp_log ("Vend Subnet...........................%s\n", inet_ntoa
(*ipaddr));
			vend += size;
			break;
		case BOOTP_HOSTNAME:
			size = (int) *vend++;
			bp_log ("Vend Hostname.........................%s\n", vend);
			vend += size;
			break;
		case BOOTP_DNS:
			size = (int) *vend++;
			for(i = 0; i < (size / 4); i++) {
				ipaddr = (int32 *) vend;
				bp_log ("Vend DomainNS.........................%s\n", 
					inet_ntoa (*ipaddr));
				vend += 4;
			}
			break;
		case BOOTP_GATEWAY:
			size = (int) *vend++;
			for(i = 0; i < (size / 4); i++) {
				ipaddr = (int32 *) vend;
				bp_log ("Vend Gateway..........................%s\n", 
					inet_ntoa (*ipaddr));
				vend += 4;
			}
			break;
			
			
		default:		/* variable field we don't know about */
			size = *vend++;
			vend += size;
			break;
	}

}


static char cookie[5] = {99, 130, 83, 99, 0};

static void
vend_fill (vend, iface, hp)
char *vend;
struct iface *iface;
struct host *hp;
{
	int 	len;
	int	mod;
	int	i;
	char	*sizep;

	/* Magic cookie */
	strcpy (vend, cookie); 
	vend += 4;

	/* Send the iface subnet */
	/* Pad so number falls on word boundry */
	
	vend++;
	vend++;

	*vend = BOOTP_SUBNET;
	vend++;
	*vend = 4;
	vend++;
	put32 (vend, iface->netmask);
	vend += 4;

	
	/* Send the DNS */
	if(bp_DefaultDomainNS[0] != 0) {
		/* Pad for allignment */
		vend++;
		vend++;

		*vend = BOOTP_DNS;
		vend++;
		sizep = vend;
		vend++;
		for(i = 0; (i < BP_MAXDNS) && (bp_DefaultDomainNS[i] != 0); i++) {
			put32 (vend, bp_DefaultDomainNS[i]);
			*sizep = *sizep + 4;
			vend += 4;
		}
	}

	/* Send the default gateway */
	if(R_default.iface == iface) {
		vend++;
		vend++;

		*vend = BOOTP_GATEWAY; 
		vend++;	
		*vend = 4;	
		vend++;
		put32 (vend, R_default.gateway);
		vend +=  4;
	}
	
	/* Send the hostname */
	if(hp != NULLHOST) {
		/* Pad so name begins on a word boundry */
		vend++;
		vend++;

		*vend = BOOTP_HOSTNAME;
		vend++;
		*vend = len = strlen (hp->name) + 1;
		vend++;
		strcpy (vend, hp->name);
		vend += len;

		/* Pad to a word. */
		mod = 4 - (len % 4);
		for(i = 0; i < mod; i++) {
			*vend = BOOTP_PAD;
			vend++;
		}
	}

	/* Mark the end of the data */
	*vend = BOOTP_END;
}



/*
 * Send a reply packet to the client.  'forward' flag is set if we are
 * not the originator of this reply packet.
 */
static void
sendreply(bp, iface)
struct bootp *bp;
struct iface *iface;
{
	struct mbuf *buf;
	int32 faddr;
	int16 length;
	char *cp;

	/*
	 * If the client IP address is specified, use that
	 * else if gateway IP address is specified, use that
	 * else make a temporary arp cache entry for the client's NEW 
	 * IP/hardware address and use that.
	 */
	if(bp->ciaddr.s_addr) {
		faddr = get32 ((char *) &(bp->ciaddr));
	} else {
		faddr = get32 ((char *) &(bp->yiaddr));
        arp_add (faddr, (int16) bp->htype, bp->chaddr, 0, iface);
	}

	if((buf = qdata ((char *)bp, sizeof (struct bootp))) == NULLBUF)
		return;
	if((buf = pushdown (buf, UDPHDR)) == NULLBUF)
		return;

	length = sizeof (struct bootp) + UDPHDR;

	cp = buf->data;
	cp = put16 (cp, IPPORT_BOOTPS);		/* Source */
	cp = put16 (cp, IPPORT_BOOTPC);		/* Dest */
	cp = put16 (cp, length);
	*cp++ = 0;
	*cp = 0;

	ip_send (iface->addr, faddr, UDP_PTCL, 0, 0, buf, length, 0, 0);
};





