/* Copyright 1986 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in file notice.h */
#include	"notice.h"

/* Implements a forwarder for bootp packets */

/*
 *------------------------------------------------------------------
 *
 * $Source: /u1/jis/gw/cgw/src/gw/in/RCS/bootp.c,v $
 * $Revision: 1.4 $
 * $Date: 89/05/22 21:24:57 $
 * $State: Exp $
 * $Author: jon $
 * $Locker:  $
 *
 * $Log:	bootp.c,v $
 * Revision 1.4  89/05/22  21:24:57  jon
 * Fix bug which caused forwarding to garbage address if a bootp request
 * which couldn't be (or shouldn't be) forwarded appears at the wrong time.
 * 
 * Revision 1.3  89/03/22  11:09:32  jon
 * change to fill in address on slip line bootp request (not interface + 1)
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_bootp_c = "$Header: bootp.c,v 1.4 89/05/22 21:24:57 jon Exp $";
#endif	lint

#include <types.h>
#include <sys.h>
#include "../src/const.h"
#include "../src/param.h"
#include "../src/defs.h"
#include "../src/macs.h"
#include "../src/net.h"
#include "../src/ext.h"
#include "in.h"
#include "inext.h"
#include "inudp.h"

#include "bootp.h"

/* The array is a list of addresses of bootp servers.  It should be
 * terminated by an address of 0.0.0.0.  Bootp_server points to the
 * next server to use.  After each use, the pointer is incremented so
 * all the servers are tried. */
ext inaddr bootp_server_list[];
inaddr *bootp_server = bootp_server_list;

/* messages */
static char hcx[] = "BOOTP hop cnt exceeded (dsc)\n";
static char ndir[] = "BOOTP not snt drct %d.%d.%d.%d -> %d.%d.%d.%d dsc\n";
static char norte[] = "BOOTP no route for pkt to %d.%d.%d.%d\n";
static char bpdsc[] = "BOOTP dsc pkt for %d.%d.%d.%d code %d\n";
static char bdop[] = "BOOTP bd op %d frm net %d intfc %s/%d\n";
static char fwd[] = "BOOTP %d.%d.%d.%d -> %d.%d.%d.%d\n";

/* Handle bootp packets sent to the bootp-server port.  If the packet
 * is a request, then fill in the gateway field and forward it off to
 * some bootp server.  If forwarding the packet would send it back
 * over the same net it came in on, then just forget about it, someone
 * else will handle forwrding the request.  If the boot request came
 * from a vii ring, then fill in the client's internet address also.
 * This is a hack to make it all work when booting through gateways.
 * Otherwise, there is not enough information for the bootp server to
 * know who is requesting the boot.  If it's a reply, then broadcast
 * it on the appropriate net.  This is necessary for hosts which are
 * on an ethernet but don't know their internet address. */
bootp_in(iob)
iorb *iob;
{
    int code;
    inaddr addr;
    net *inhop();
    net *netp;
    net *snetp;
    inpkt *ipkt;
    udppkt *upkt;
    struct bootp_pkt *bpkt;

    netp = mknet(iob->i_usr2);
    ipkt = (inpkt *)iob->i_addr;
    upkt = (udppkt *)((byte *)ipkt + sizeof(inpkt));
    bpkt = (struct bootp_pkt *)((byte *)upkt + sizeof(udppkt));
    if (bpkt->bp_op == BOOTREQUEST) {
	ipkt->i_dst.i_long = (bootp_server++)->i_long;
	if (bootp_server->i_long == 0)
	  bootp_server = bootp_server_list;
	if ((snetp = inhop(iob, &addr)) == NULL) {
	    niflog(L_ERRU)
	      dolog(norte, mksina(ipkt->i_dst));
	    return;
	}
	if (snetp == netp) {
	    freebuf(iob);
	    return;
	}
	ipkt->i_src.i_long = mkina(iniatlst[snetp->n_net]->inia_addr)->i_long;
	ipkt->i_cksum = 0;
	ipkt->i_cksum = ~cksum(ipkt, INMINH * INWPHW, 0);
	if (bpkt->bp_hops++ > 3) {
	    niflog(L_ERRU)
	      dolog(hcx);
	    freebuf(iob);
	    return;
	}
	if (bpkt->bp_giaddr == 0) {
	    bpkt->bp_giaddr = mkina(iniatlst[netp->n_net]->inia_addr)->i_long;

	    /* If client is on a vii ringnet, fill in the client address. */
	    if (bpkt->bp_htype == TYPE_VII) {
		if (bpkt->bp_ciaddr == 0) {
		    bpkt->bp_ciaddr = bpkt->bp_giaddr;
		    ((inaddr *)(&bpkt->bp_ciaddr))->i_saddr.i_shst = bpkt->bp_chaddr[0];
		}
	    } else if (bpkt->bp_htype == TYPE_SLIP) {
	    /* If the client is a host on the far side of a point to point
	       link for which we're doing proxy, then return our interface
	       IP address for the client's IP address. */
		if ((bpkt->bp_ciaddr == 0) &&
		    (iniatlst[netp->n_net]->inia_flags & IF_PPP_PROXY))
		  bpkt->bp_ciaddr = bpkt->bp_giaddr;
	    }
	}
	niflog(L_TRCO)
	  dolog(fwd, mksina((inaddr *)&bpkt->bp_ciaddr), mksina(&ipkt->i_dst));
	inxaddq(iob);
	return;
    }
    else if (bpkt->bp_op == BOOTREPLY) {
	if (bpkt->bp_ciaddr != 0) {
	    /* This shouldn't happen because the packet should have
	     * been sent directly to the client. */
	    niflog(L_ERRU)
	      dolog(ndir, mksina((inaddr *)&bpkt->bp_siaddr),
		    mksina((inaddr *)&bpkt->bp_ciaddr));
	    freebuf(iob);
	    return;
	}
	ipkt->i_src.i_long = bpkt->bp_giaddr;
	ipkt->i_dst.i_long = bpkt->bp_yiaddr;
	ipkt->i_cksum = 0;
	ipkt->i_cksum = ~cksum(ipkt, INMINH * INWPHW, 0);
	if ((netp = inhop(iob, &addr)) == NULL) {
	    niflog(L_ERRU)
	      dolog(norte, mksina(&ipkt->i_dst));
	    return;
	}
	if ((code = (*netp->n_send)(iob, netp, P_IN, ANYHOST)) != D_OK) {
	    niflog(L_ERRU)
	      dolog(bpdsc, mksina(&ipkt->i_dst), code);
	    freebuf(iob);
	    return;
	}
    }
    else {
	niflog(L_ERRU)
	  dolog(bdop, bpkt->bp_op, mksnet(netp));
	freebuf(iob);
	return;
    }
}
