/* bootp.c

   BOOTP Protocol support. */

/*
 * Copyright (c) 1995, 1996, 1998, 1999 The Internet Software Consortium.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of The Internet Software Consortium nor the names
 *    of its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This software has been written for the Internet Software Consortium
 * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
 * Enterprises.  To learn more about the Internet Software Consortium,
 * see ``http://www.vix.com/isc''.  To learn more about Vixie
 * Enterprises, see ``http://www.vix.com''.
 */

#ifndef lint
static char copyright[] =
"$Id: bootp.c,v 1.28.2.9 1999/10/19 17:32:49 mellon Exp $ Copyright (c) 1995, 1996, 1998, 1999 The Internet Software Consortium.  All rights reserved.\n";
#endif /* not lint */

#include "dhcpd.h"

void bootp (packet)
	struct packet *packet;
{
	int result;
	struct host_decl *hp;
	struct host_decl *host = (struct host_decl *)0;
	struct packet outgoing;
	struct dhcp_packet raw;
	struct sockaddr_in to;
	struct in_addr from;
	struct hardware hto;
	struct tree_cache *options [256];
	struct subnet *subnet;
	struct lease *lease;
	struct iaddr ip_address;
	int i;

	if (packet -> raw -> op != BOOTREQUEST)
		return;

	note ("BOOTREQUEST from %s via %s%s",
	      print_hw_addr (packet -> raw -> htype,
			     packet -> raw -> hlen,
			     packet -> raw -> chaddr),
	      packet -> raw -> giaddr.s_addr
	      ? inet_ntoa (packet -> raw -> giaddr)
	      : packet -> interface -> name,
	      packet -> options_valid ? "" : " (non-rfc1048)");



	if (!locate_network (packet))
		return;

	hp = find_hosts_by_haddr (packet -> raw -> htype,
				  packet -> raw -> chaddr,
				  packet -> raw -> hlen);

	lease = find_lease (packet, packet -> shared_network, 0);

	/* Find an IP address in the host_decl that matches the
	   specified network. */
	if (hp)
		subnet = find_host_for_network (&hp, &ip_address,
						packet -> shared_network);
	else
		subnet = (struct subnet *)0;

	if (!subnet) {
		/* We didn't find an applicable host declaration.
		   Just in case we may be able to dynamically assign
		   an address, see if there's a host declaration
		   that doesn't have an ip address associated with it. */
		if (hp) {
			for (; hp; hp = hp -> n_ipaddr) {
				if (!hp -> fixed_addr) {
					host = hp;
					break;
				}
			}
		}

		if (host && (!host -> group -> allow_booting)) {
			note ("Ignoring excluded BOOTP client %s",
			      host -> name
			      ? host -> name
			      : print_hw_addr (packet -> raw -> htype,
					       packet -> raw -> hlen,
					       packet -> raw -> chaddr));
			return;
		}
			
		if (host && (!host -> group -> allow_bootp)) {
			note ("Ignoring BOOTP request from client %s",
			      host -> name
			      ? host -> name
			      : print_hw_addr (packet -> raw -> htype,
					       packet -> raw -> hlen,
					       packet -> raw -> chaddr));
			return;
		}
			
		/* If we've been told not to boot unknown clients,
		   and we didn't find any host record for this client,
		   ignore it. */
		if (!host && !(packet -> shared_network ->
			       group -> boot_unknown_clients)) {
			note ("Ignoring unknown BOOTP client %s via %s",
			      print_hw_addr (packet -> raw -> htype,
					     packet -> raw -> hlen,
					     packet -> raw -> chaddr),
			      packet -> raw -> giaddr.s_addr
			      ? inet_ntoa (packet -> raw -> giaddr)
			      : packet -> interface -> name);
			return;
		}

		/* If we've been told not to boot with bootp on this
		   network, ignore it. */
		if (!host &&
		    !(packet -> shared_network -> group -> allow_bootp)) {
			note ("Ignoring BOOTP request from client %s via %s",
			      print_hw_addr (packet -> raw -> htype,
					     packet -> raw -> hlen,
					     packet -> raw -> chaddr),
			      packet -> raw -> giaddr.s_addr
			      ? inet_ntoa (packet -> raw -> giaddr)
			      : packet -> interface -> name);
			return;
		}

		/* If the packet is from a host we don't know and there
		   are no dynamic bootp addresses on the network it came
		   in on, drop it on the floor. */
		if (!(packet -> shared_network -> group -> dynamic_bootp)) {
		      lose:
			note ("No applicable record for BOOTP host %s via %s",
			      print_hw_addr (packet -> raw -> htype,
					     packet -> raw -> hlen,
					     packet -> raw -> chaddr),
			      packet -> raw -> giaddr.s_addr
			      ? inet_ntoa (packet -> raw -> giaddr)
			      : packet -> interface -> name);
			return;
		}

		/* If a lease has already been assigned to this client
		   and it's still okay to use dynamic bootp on
		   that lease, reassign it. */
		if (lease) {
			/* If this lease can be used for dynamic bootp,
			   do so. */
			if ((lease -> flags & DYNAMIC_BOOTP_OK)) {

				/* If it's not a DYNAMIC_BOOTP lease,
				   release it before reassigning it
				   so that we don't get a lease
				   conflict. */
				if (!(lease -> flags & BOOTP_LEASE))
					release_lease (lease);

				lease -> host = host;
				ack_lease (packet, lease, 0, 0);
				return;
			}

			 /* If dynamic BOOTP is no longer allowed for
			   this lease, set it free. */
			release_lease (lease);
		}

		/* If there are dynamic bootp addresses that might be
		   available, try to snag one. */
		for (lease = packet -> shared_network -> last_lease;
		     lease && lease -> ends <= cur_time;
		     lease = lease -> prev) {
			if ((lease -> flags & DYNAMIC_BOOTP_OK)) {
				lease -> host = host;
				ack_lease (packet, lease, 0, 0);
				return;
			}
		}
		goto lose;
	}

	/* Make sure we're allowed to boot this client. */
	if (hp && (!hp -> group -> allow_booting)) {
		note ("Ignoring excluded BOOTP client %s",
		      hp -> name);
		return;
	}
			
	/* Make sure we're allowed to boot this client with bootp. */
	if (hp && (!hp -> group -> allow_bootp)) {
		note ("Ignoring BOOTP request from client %s",
		      hp -> name);
		return;
	}
			
	/* Set up the outgoing packet... */
	memset (&outgoing, 0, sizeof outgoing);
	memset (&raw, 0, sizeof raw);
	outgoing.raw = &raw;

	/* If we didn't get a known vendor magic number on the way in,
	   just copy the input options to the output. */
	if (!packet -> options_valid &&
	    !subnet -> group -> always_reply_rfc1048 &&
	    (!hp || !hp -> group -> always_reply_rfc1048)) {
		memcpy (outgoing.raw -> options,
			packet -> raw -> options, DHCP_OPTION_LEN);
		outgoing.packet_length = BOOTP_MIN_LEN;
	} else {
		struct tree_cache netmask_tree;   /*  -- RBF */

		/* Come up with a list of options that we want to send
		   to this client.  Start with the per-subnet options,
		   and then override those with client-specific
		   options. */

		memcpy (options, subnet -> group -> options, sizeof options);

		for (i = 0; i < 256; i++) {
			if (hp -> group -> options [i])
				options [i] = hp -> group -> options [i];
		}

		/* Use the subnet mask from the subnet declaration if no other
		   mask has been provided. */
		if (!options [DHO_SUBNET_MASK]) {
			options [DHO_SUBNET_MASK] = &netmask_tree;
			netmask_tree.flags = TC_TEMPORARY;
			netmask_tree.value = lease -> subnet -> netmask.iabuf;
			netmask_tree.len = lease -> subnet -> netmask.len;
			netmask_tree.buf_size = lease -> subnet -> netmask.len;
			netmask_tree.timeout = 0xFFFFFFFF;
			netmask_tree.tree = (struct tree *)0;
		}

		/* Pack the options into the buffer.  Unlike DHCP, we
		   can't pack options into the filename and server
		   name buffers. */

		outgoing.packet_length =
			cons_options (packet, outgoing.raw,
				      0, options, 0, 0, 1, (u_int8_t *)0, 0);
		if (outgoing.packet_length < BOOTP_MIN_LEN)
			outgoing.packet_length = BOOTP_MIN_LEN;
	}

	/* Take the fields that we care about... */
	raw.op = BOOTREPLY;
	raw.htype = packet -> raw -> htype;
	raw.hlen = packet -> raw -> hlen;
	memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr);
	raw.hops = packet -> raw -> hops;
	raw.xid = packet -> raw -> xid;
	raw.secs = packet -> raw -> secs;
	raw.flags = packet -> raw -> flags;
	raw.ciaddr = packet -> raw -> ciaddr;
	memcpy (&raw.yiaddr, ip_address.iabuf, sizeof raw.yiaddr);

	/* Figure out the address of the next server. */
	if (hp  && hp -> group -> next_server.len)
		memcpy (&raw.siaddr, hp -> group -> next_server.iabuf, 4);
	else if (subnet -> group -> next_server.len)
		memcpy (&raw.siaddr, subnet -> group -> next_server.iabuf, 4);
	else if (subnet -> interface_address.len)
		memcpy (&raw.siaddr, subnet -> interface_address.iabuf, 4);
	else
		raw.siaddr = packet -> interface -> primary_address;

	raw.giaddr = packet -> raw -> giaddr;
	if (hp -> group -> server_name)
		strncpy (raw.sname, hp -> group -> server_name,
			 (sizeof raw.sname));
	else if (subnet -> group -> server_name)
		strncpy (raw.sname, subnet -> group -> server_name,
			 (sizeof raw.sname));

	if (hp -> group -> filename)
		strncpy (raw.file, hp -> group -> filename,
			 (sizeof raw.file));
	else if (subnet -> group -> filename)
		strncpy (raw.file, subnet -> group -> filename,
			 (sizeof raw.file));
	else
		memcpy (raw.file, packet -> raw -> file, sizeof raw.file);

	/* Set up the hardware destination address... */
	hto.htype = packet -> raw -> htype;
	hto.hlen = packet -> raw -> hlen;
	memcpy (hto.haddr, packet -> raw -> chaddr, hto.hlen);

	from = packet -> interface -> primary_address;

	/* Report what we're doing... */
	note ("BOOTREPLY for %s to %s (%s) via %s",
	      piaddr (ip_address), hp -> name,
	      print_hw_addr (packet -> raw -> htype,
			     packet -> raw -> hlen,
			     packet -> raw -> chaddr),
	      packet -> raw -> giaddr.s_addr
	      ? inet_ntoa (packet -> raw -> giaddr)
	      : packet -> interface -> name);

	/* Set up the parts of the address that are in common. */
	to.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
	to.sin_len = sizeof to;
#endif
	memset (to.sin_zero, 0, sizeof to.sin_zero);

	/* If this was gatewayed, send it back to the gateway... */
	if (raw.giaddr.s_addr) {
		to.sin_addr = raw.giaddr;
		to.sin_port = local_port;

		if (fallback_interface) {
			result = send_packet (fallback_interface,
					      (struct packet *)0,
					      &raw, outgoing.packet_length,
					      from, &to, &hto);
			return;
		}

	/* If it comes from a client that already knows its address
	   and is not requesting a broadcast response, and we can
	   unicast to a client without using the ARP protocol, sent it
	   directly to that client. */
	} else if (!(raw.flags & htons (BOOTP_BROADCAST)) &&
		   can_unicast_without_arp ()) {
		to.sin_addr = raw.yiaddr;
		to.sin_port = remote_port;

	/* Otherwise, broadcast it on the local network. */
	} else {
		to.sin_addr.s_addr = INADDR_BROADCAST;
		to.sin_port = remote_port; /* XXX */
	}

	errno = 0;
	result = send_packet (packet -> interface,
			      packet, &raw, outgoing.packet_length,
			      from, &to, &hto);
}
