diff options
Diffstat (limited to 'server/bootp.c')
-rw-r--r-- | server/bootp.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/server/bootp.c b/server/bootp.c new file mode 100644 index 0000000..c88fab8 --- /dev/null +++ b/server/bootp.c @@ -0,0 +1,422 @@ +/* bootp.c + + BOOTP Protocol support. */ + +/* + * Copyright (c) 2004,2005,2007,2009 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1995-2003 by Internet Software Consortium + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + * This software has been written for Internet Systems Consortium + * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc. + * To learn more about Internet Systems Consortium, see + * ``https://www.isc.org/''. To learn more about Vixie Enterprises, + * see ``http://www.vix.com''. To learn more about Nominum, Inc., see + * ``http://www.nominum.com''. + */ + +#include "dhcpd.h" +#include <errno.h> + +#if defined (TRACING) +# define send_packet trace_packet_send +#endif + +void bootp (packet) + struct packet *packet; +{ + int result; + struct host_decl *hp = (struct host_decl *)0; + 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 option_state *options = (struct option_state *)0; + struct lease *lease = (struct lease *)0; + unsigned i; + struct data_string d1; + struct option_cache *oc; + char msgbuf [1024]; + int ignorep; + int peer_has_leases = 0; + + if (packet -> raw -> op != BOOTREQUEST) + return; + + /* %Audit% This is log output. %2004.06.17,Safe% + * If we truncate we hope the user can get a hint from the log. + */ + snprintf (msgbuf, sizeof msgbuf, "BOOTREQUEST from %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); + + if (!locate_network (packet)) { + log_info ("%s: network unknown", msgbuf); + return; + } + + find_lease (&lease, packet, packet -> shared_network, + 0, 0, (struct lease *)0, MDL); + + if (lease && lease->host) + host_reference(&hp, lease->host, MDL); + + if (!lease || ((lease->flags & STATIC_LEASE) == 0)) { + struct host_decl *h; + + /* We didn't find an applicable fixed-address 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) + find_hosts_by_haddr(&hp, packet->raw->htype, + packet->raw->chaddr, + packet->raw->hlen, MDL); + + for (h = hp; h; h = h -> n_ipaddr) { + if (!h -> fixed_addr) { + host_reference(&host, h, MDL); + break; + } + } + + if (hp) + host_dereference(&hp, MDL); + + if (host) { + host_reference(&hp, host, MDL); + host_dereference(&host, MDL); + } + + /* Allocate a lease if we have not yet found one. */ + if (!lease) + allocate_lease (&lease, packet, + packet -> shared_network -> pools, + &peer_has_leases); + + if (lease == NULL) { + log_info("%s: BOOTP from dynamic client and no " + "dynamic leases", msgbuf); + goto out; + } + +#if defined(FAILOVER_PROTOCOL) + if ((lease->pool != NULL) && + (lease->pool->failover_peer != NULL)) { + dhcp_failover_state_t *peer; + + peer = lease->pool->failover_peer; + + /* If we are in a failover state that bars us from + * answering, do not do so. + * If we are in a cooperative state, load balance + * (all) responses. + */ + if ((peer->service_state == not_responding) || + (peer->service_state == service_startup)) { + log_info("%s: not responding%s", + msgbuf, peer->nrr); + goto out; + } else if((peer->service_state == cooperating) && + !load_balance_mine(packet, peer)) { + log_info("%s: load balance to peer %s", + msgbuf, peer->name); + goto out; + } + } +#endif + + ack_lease (packet, lease, 0, 0, msgbuf, 0, hp); + goto out; + } + + /* Run the executable statements to compute the client and server + options. */ + option_state_allocate (&options, MDL); + + /* Execute the subnet statements. */ + execute_statements_in_scope ((struct binding_value **)0, + packet, lease, (struct client_state *)0, + packet -> options, options, + &lease -> scope, lease -> subnet -> group, + (struct group *)0); + + /* Execute statements from class scopes. */ + for (i = packet -> class_count; i > 0; i--) { + execute_statements_in_scope + ((struct binding_value **)0, + packet, lease, (struct client_state *)0, + packet -> options, options, + &lease -> scope, packet -> classes [i - 1] -> group, + lease -> subnet -> group); + } + + /* Execute the host statements. */ + execute_statements_in_scope ((struct binding_value **)0, + packet, lease, (struct client_state *)0, + packet -> options, options, + &lease -> scope, + hp -> group, lease -> subnet -> group); + + /* Drop the request if it's not allowed for this client. */ + if ((oc = lookup_option (&server_universe, options, SV_ALLOW_BOOTP)) && + !evaluate_boolean_option_cache (&ignorep, packet, lease, + (struct client_state *)0, + packet -> options, options, + &lease -> scope, oc, MDL)) { + if (!ignorep) + log_info ("%s: bootp disallowed", msgbuf); + goto out; + } + + if ((oc = lookup_option (&server_universe, + options, SV_ALLOW_BOOTING)) && + !evaluate_boolean_option_cache (&ignorep, packet, lease, + (struct client_state *)0, + packet -> options, options, + &lease -> scope, oc, MDL)) { + if (!ignorep) + log_info ("%s: booting disallowed", msgbuf); + goto out; + } + + /* 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 && + !(evaluate_boolean_option_cache + (&ignorep, packet, lease, (struct client_state *)0, + packet -> options, options, &lease -> scope, + lookup_option (&server_universe, options, + SV_ALWAYS_REPLY_RFC1048), MDL))) { + memcpy (outgoing.raw -> options, + packet -> raw -> options, DHCP_MAX_OPTION_LEN); + outgoing.packet_length = BOOTP_MIN_LEN; + } else { + + /* Use the subnet mask from the subnet declaration if no other + mask has been provided. */ + + oc = (struct option_cache *)0; + i = DHO_SUBNET_MASK; + if (!lookup_option (&dhcp_universe, options, i)) { + if (option_cache_allocate (&oc, MDL)) { + if (make_const_data + (&oc -> expression, + lease -> subnet -> netmask.iabuf, + lease -> subnet -> netmask.len, + 0, 0, MDL)) { + option_code_hash_lookup(&oc->option, + dhcp_universe.code_hash, + &i, 0, MDL); + save_option (&dhcp_universe, + options, oc); + } + option_cache_dereference (&oc, MDL); + } + } + + /* 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, lease, + (struct client_state *)0, 0, + packet -> options, options, + &lease -> scope, + 0, 0, 1, (struct data_string *)0, + (const char *)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; + + /* yiaddr is an ipv4 address, it must be 4 octets. */ + memcpy (&raw.yiaddr, lease->ip_addr.iabuf, 4); + + /* If we're always supposed to broadcast to this client, set + the broadcast bit in the bootp flags field. */ + if ((oc = lookup_option (&server_universe, + options, SV_ALWAYS_BROADCAST)) && + evaluate_boolean_option_cache (&ignorep, packet, lease, + (struct client_state *)0, + packet -> options, options, + &lease -> scope, oc, MDL)) + raw.flags |= htons (BOOTP_BROADCAST); + + /* Figure out the address of the next server. */ + memset (&d1, 0, sizeof d1); + oc = lookup_option (&server_universe, options, SV_NEXT_SERVER); + if (oc && + evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, options, + &lease -> scope, oc, MDL)) { + /* If there was more than one answer, take the first. */ + if (d1.len >= 4 && d1.data) + memcpy (&raw.siaddr, d1.data, 4); + data_string_forget (&d1, MDL); + } else { + if ((lease->subnet->shared_network->interface != NULL) && + lease->subnet->shared_network->interface->address_count) + raw.siaddr = + lease->subnet->shared_network->interface->addresses[0]; + else if (packet->interface->address_count) + raw.siaddr = packet->interface->addresses[0]; + } + + raw.giaddr = packet -> raw -> giaddr; + + /* Figure out the filename. */ + oc = lookup_option (&server_universe, options, SV_FILENAME); + if (oc && + evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, options, + &lease -> scope, oc, MDL)) { + memcpy (raw.file, d1.data, + d1.len > sizeof raw.file ? sizeof raw.file : d1.len); + if (sizeof raw.file > d1.len) + memset (&raw.file [d1.len], + 0, (sizeof raw.file) - d1.len); + data_string_forget (&d1, MDL); + } else + memcpy (raw.file, packet -> raw -> file, sizeof raw.file); + + /* Choose a server name as above. */ + oc = lookup_option (&server_universe, options, SV_SERVER_NAME); + if (oc && + evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, options, + &lease -> scope, oc, MDL)) { + memcpy (raw.sname, d1.data, + d1.len > sizeof raw.sname ? sizeof raw.sname : d1.len); + if (sizeof raw.sname > d1.len) + memset (&raw.sname [d1.len], + 0, (sizeof raw.sname) - d1.len); + data_string_forget (&d1, MDL); + } + + /* Execute the commit statements, if there are any. */ + execute_statements ((struct binding_value **)0, + packet, lease, (struct client_state *)0, + packet -> options, + options, &lease -> scope, lease -> on_commit); + + /* We're done with the option state. */ + option_state_dereference (&options, MDL); + + /* Set up the hardware destination address... */ + hto.hbuf [0] = packet -> raw -> htype; + hto.hlen = packet -> raw -> hlen + 1; + memcpy (&hto.hbuf [1], packet -> raw -> chaddr, packet -> raw -> hlen); + + if (packet->interface->address_count) { + from = packet->interface->addresses[0]; + } else { + log_error("%s: Interface %s appears to have no IPv4 " + "addresses, and so dhcpd cannot select a source " + "address.", msgbuf, packet->interface->name); + goto out; + } + + /* Report what we're doing... */ + log_info ("%s", msgbuf); + log_info ("BOOTREPLY for %s to %s (%s) via %s", + piaddr (lease->ip_addr), 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); + goto out; + } + + /* 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 (packet -> interface)) { + to.sin_addr = raw.yiaddr; + to.sin_port = remote_port; + + /* Otherwise, broadcast it on the local network. */ + } else { + to.sin_addr = limited_broadcast; + to.sin_port = remote_port; /* XXX */ + } + + errno = 0; + result = send_packet (packet -> interface, + packet, &raw, outgoing.packet_length, + from, &to, &hto); + out: + if (options) + option_state_dereference (&options, MDL); + if (lease) + lease_dereference (&lease, MDL); + if (hp) + host_dereference (&hp, MDL); + if (host) + host_dereference (&host, MDL); +} |