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

/* JIS points out that the last sentence isn't true.  We can just send
   to their ethernet address, since it's already in the bootp packet! */

bootp_in(iob)
iorb *iob;
{
    inia *inina;
    int sent_one;
    inaddr addr;
    net *inhop();
    net *src_netp;
    net *dst_netp;
    inpkt *ipkt;
    udppkt *upkt;
    struct bootp_pkt *bpkt;

    src_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 ((dst_netp = inhop(iob, &addr)) == NULL) {
	    niflog(L_ERRU)
	      dolog(norte, mksina(&ipkt->i_dst));
	    return;
	}
	if (dst_netp == src_netp) {
	    freebuf(iob);
	    return;
	}
	ipkt->i_src.i_long = 
	  mkina(iniatlst[dst_netp->n_net]->inia_addr)->i_long;
	ipkt->i_cksum = 0;
	ipkt->i_cksum = ~cksum(ipkt, INMINH * INWPHW, 0);
	if (++(bpkt->bp_hops) > 2) {
	    niflog(L_ERRU)
	      dolog(hcx);
	    freebuf(iob);
	    return;
	}
	if (bpkt->bp_giaddr == 0) {
	    bpkt->bp_giaddr =
	      mkina(iniatlst[src_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[src_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_hops) > 2) {
	    niflog(L_ERRU)
	      dolog(hcx);
	    freebuf(iob);
	    return;
	}
	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);

	/* We should forward the packet to the client.  Since we don't
	   remember what interface the original request came in on we have
	   to send to all interfaces of the right type.

	   However, if we have an IP address we can get the correct interface
	   from the routing table */

	if (ipkt->i_dst.i_long != 0) {
	  if ((dst_netp = inhop(iob, &addr)) == NULL) {
	    niflog(L_ERRU)
	      dolog(norte, mksina(&ipkt->i_dst));
	    /* inhop has already freed the iob if there was no route */
	    return;
	  }
	  if (dst_netp == src_netp) {
	    niflog(L_ERRU)
	      dolog("BOOTP reply would be routed out incoming interface.\n");
	    freebuf(iob);
	    return;
	  }
	  if (bpkt->bp_htype != dst_netp->n_type) {
	    niflog(L_ERRU)
	      dolog("BOOTP reply interface wrong type (%d not %d).\n",
		    bpkt->bp_htype, dst_netp->n_type);
	    freebuf(iob);
	    return;
	  }
	  forward_bootp_reply(dst_netp, iob, ipkt);
	}
	else {
	    /* Don't know what interface, must try all with the correct
	       hardware type ... */
	  int sent_once = 0;
	  for (dst_netp = nets; dst_netp < lstnet; dst_netp++)
	    if (bpkt->bp_htype == dst_netp->n_type) {
	      if (sent_once) {
		niflog(L_ERRU) {
		  dolog("BOOTP implementation limitation.  Can only send 1 reply, more than 1 interface is of correct type.\n");
		  }
		return;
	      }
	      forward_bootp_reply(dst_netp, iob, ipkt);
	      sent_once = 1;
	    }
	  if (!sent_once) {
	    niflog(L_ERRU) {
	      dolog("BOOTP not interface of correct type (%d) for reply.\n",
		    bpkt->bp_htype);
	      freebuf(iob);
	      return;
	    }
	  }
	}
      }
    else { 
        /* Unkown Bootp OP code */
	niflog(L_ERRU)
	  dolog(bdop, bpkt->bp_op, mksnet(src_netp));
	freebuf(iob);
	return;
      }
  }


forward_bootp_reply(netp, iob, ipkt)
     net *netp;
     iorb *iob;
     inpkt *ipkt;
{
  int code;
  
  /* This should unicast to the correct hardware address! */
  
  niflog(L_TRCO) 
    dolog("BOOTP foward reply to mksina(&ipkt->i_dst
, mksina((inaddr *)&bpkt->bp_ciaddr), mksina(&ipkt->i_dst));

  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;
  }
}
