diff options
author | Henning Brauer <henning@cvs.openbsd.org> | 2004-04-13 23:41:50 +0000 |
---|---|---|
committer | Henning Brauer <henning@cvs.openbsd.org> | 2004-04-13 23:41:50 +0000 |
commit | d4e911c1cc791c196fcb9c9d13ec28a25e63053f (patch) | |
tree | aa8ed389eb5fc83d380043d222e533186e278b0f /usr.sbin/dhcpd/dhcp.c | |
parent | deece1dfb12ee6671c915bc387ca2298c3520269 (diff) |
may the whacking begin
Diffstat (limited to 'usr.sbin/dhcpd/dhcp.c')
-rw-r--r-- | usr.sbin/dhcpd/dhcp.c | 1732 |
1 files changed, 1732 insertions, 0 deletions
diff --git a/usr.sbin/dhcpd/dhcp.c b/usr.sbin/dhcpd/dhcp.c new file mode 100644 index 00000000000..eca34e64d60 --- /dev/null +++ b/usr.sbin/dhcpd/dhcp.c @@ -0,0 +1,1732 @@ +/* dhcp.c + + DHCP Protocol engine. */ + +/* + * Copyright (c) 1995, 1996, 1997, 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''. + */ + +#include "dhcpd.h" + +int outstanding_pings; + +static char dhcp_message [256]; + +void dhcp (packet) + struct packet *packet; +{ + if (!locate_network (packet) && packet -> packet_type != DHCPREQUEST) + return; + + switch (packet -> packet_type) { + case DHCPDISCOVER: + dhcpdiscover (packet); + break; + + case DHCPREQUEST: + dhcprequest (packet); + break; + + case DHCPRELEASE: + dhcprelease (packet); + break; + + case DHCPDECLINE: + dhcpdecline (packet); + break; + + case DHCPINFORM: + dhcpinform (packet); + break; + + default: + break; + } +} + +void dhcpdiscover (packet) + struct packet *packet; +{ + struct lease *lease = find_lease (packet, packet -> shared_network, 0); + struct host_decl *hp; + + note ("DHCPDISCOVER 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); + + /* Sourceless packets don't make sense here. */ + if (!packet -> shared_network) { + note ("Packet from unknown subnet: %s", + inet_ntoa (packet -> raw -> giaddr)); + return; + } + + /* If we didn't find a lease, try to allocate one... */ + if (!lease) { + lease = packet -> shared_network -> last_lease; + + /* If there are no leases in that subnet that have + expired, we have nothing to offer this client. */ + if (!lease || lease -> ends > cur_time) { + note ("no free leases on subnet %s", + packet -> shared_network -> name); + return; + } + + /* If we find an abandoned lease, take it, but print a + warning message, so that if it continues to lose, + the administrator will eventually investigate. */ + if ((lease -> flags & ABANDONED_LEASE)) { + struct lease *lp; + + /* See if we can find an unabandoned lease first. */ + for (lp = lease; lp; lp = lp -> prev) { + if (lp -> ends > cur_time) + break; + if (!(lp -> flags & ABANDONED_LEASE)) { + lease = lp; + break; + } + } + + /* If we can't find an unabandoned lease, reclaim the + abandoned lease. */ + if ((lease -> flags & ABANDONED_LEASE)) { + warn ("Reclaiming abandoned IP address %s.", + piaddr (lease -> ip_addr)); + lease -> flags &= ~ABANDONED_LEASE; + } + } + + /* Try to find a host_decl that matches the client + identifier or hardware address on the packet, and + has no fixed IP address. If there is one, hang + it off the lease so that its option definitions + can be used. */ + if (((packet -> options [DHO_DHCP_CLIENT_IDENTIFIER].len + != 0) && + ((hp = find_hosts_by_uid + (packet -> options [DHO_DHCP_CLIENT_IDENTIFIER].data, + packet -> options [DHO_DHCP_CLIENT_IDENTIFIER].len)) + != (struct host_decl *)0)) || + ((hp = find_hosts_by_haddr (packet -> raw -> htype, + packet -> raw -> chaddr, + packet -> raw -> hlen)) + != (struct host_decl *)0)) { + for (; hp; hp = hp -> n_ipaddr) { + if (!hp -> fixed_addr) { + lease -> host = hp; + break; + } + } + } else { + lease -> host = (struct host_decl *)0; + } + } + + /* If this subnet won't boot unknown clients, ignore the + request. */ + if (!lease -> host && + !lease -> subnet -> group -> boot_unknown_clients) { + note ("Ignoring unknown client %s", + print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr)); + } else if (lease -> host && + !lease -> host -> group -> allow_booting) { + note ("Declining to boot client %s", + lease -> host -> name + ? lease -> host -> name + : print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr)); + } else + ack_lease (packet, lease, DHCPOFFER, cur_time + 120); +} + +void dhcprequest (packet) + struct packet *packet; +{ + struct lease *lease; + struct iaddr cip; + struct subnet *subnet; + int ours = 0; + + if (packet -> options [DHO_DHCP_REQUESTED_ADDRESS].len) { + cip.len = 4; + memcpy (cip.iabuf, + packet -> options [DHO_DHCP_REQUESTED_ADDRESS].data, + 4); + } else { + cip.len = 4; + memcpy (cip.iabuf, &packet -> raw -> ciaddr.s_addr, 4); + } + subnet = find_subnet (cip); + + /* Find the lease that matches the address requested by the + client. */ + + if (subnet) + lease = find_lease (packet, subnet -> shared_network, &ours); + else + lease = (struct lease *)0; + + note ("DHCPREQUEST for %s from %s via %s", + piaddr (cip), + 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 a client on a given network REQUESTs a lease on an + address on a different network, NAK it. If the Requested + Address option was used, the protocol says that it must + have been broadcast, so we can trust the source network + information. + + If ciaddr was specified and Requested Address was not, then + we really only know for sure what network a packet came from + if it came through a BOOTP gateway - if it came through an + IP router, we'll just have to assume that it's cool. + + If we don't think we know where the packet came from, it + came through a gateway from an unknown network, so it's not + from a RENEWING client. If we recognize the network it + *thinks* it's on, we can NAK it even though we don't + recognize the network it's *actually* on; otherwise we just + have to ignore it. + + We don't currently try to take advantage of access to the + raw packet, because it's not available on all platforms. + So a packet that was unicast to us through a router from a + RENEWING client is going to look exactly like a packet that + was broadcast to us from an INIT-REBOOT client. + + Since we can't tell the difference between these two kinds + of packets, if the packet appears to have come in off the + local wire, we have to treat it as if it's a RENEWING + client. This means that we can't NAK a RENEWING client on + the local wire that has a bogus address. The good news is + that we won't ACK it either, so it should revert to INIT + state and send us a DHCPDISCOVER, which we *can* work with. + + Because we can't detect that a RENEWING client is on the + wrong wire, it's going to sit there trying to renew until + it gets to the REBIND state, when we *can* NAK it because + the packet will get to us through a BOOTP gateway. We + shouldn't actually see DHCPREQUEST packets from RENEWING + clients on the wrong wire anyway, since their idea of their + local router will be wrong. In any case, the protocol + doesn't really allow us to NAK a DHCPREQUEST from a + RENEWING client, so we can punt on this issue. */ + + if (!packet -> shared_network || + (packet -> raw -> ciaddr.s_addr && + packet -> raw -> giaddr.s_addr) || + (packet -> options [DHO_DHCP_REQUESTED_ADDRESS].len && + !packet -> raw -> ciaddr.s_addr)) { + + /* If we don't know where it came from but we do know + where it claims to have come from, it didn't come + from there. Fry it. */ + if (!packet -> shared_network) { + if (subnet && + subnet -> shared_network -> group -> authoritative) + { + nak_lease (packet, &cip); + return; + } + /* Otherwise, ignore it. */ + return; + } + + /* If we do know where it came from and it asked for an + address that is not on that shared network, nak it. */ + subnet = find_grouped_subnet (packet -> shared_network, cip); + if (!subnet) { + if (packet -> shared_network -> group -> authoritative) + nak_lease (packet, &cip); + return; + } + } + + /* If we found a lease for the client but it's not the one the + client asked for, don't send it - some other server probably + made the cut. */ + if (lease && !addr_eq (lease -> ip_addr, cip)) { + /* If we found the address the client asked for, but + it wasn't what got picked, the lease belongs to us, + so we should NAK it. */ + if (ours) + nak_lease (packet, &cip); + return; + } + + /* If the address the client asked for is ours, but it wasn't + available for the client, NAK it. */ + if (!lease && ours) { + nak_lease (packet, &cip); + return; + } + + /* If we're not allowed to serve this client anymore, don't. */ + if (lease && + !lease -> host && + !lease -> subnet -> group -> boot_unknown_clients) { + note ("Ignoring unknown client %s", + print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr)); + return; + } else if (lease && lease -> host && + !lease -> host -> group -> allow_booting) { + note ("Declining to renew client %s", + lease -> host -> name + ? lease -> host -> name + : print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr)); + return; + } + + /* If we own the lease that the client is asking for, + and it's already been assigned to the client, ack it. */ + if (lease && + ((lease -> uid_len && lease -> uid_len == + packet -> options [DHO_DHCP_CLIENT_IDENTIFIER].len && + !memcmp (packet -> options + [DHO_DHCP_CLIENT_IDENTIFIER].data, + lease -> uid, lease -> uid_len)) || + (lease -> hardware_addr.hlen == packet -> raw -> hlen && + lease -> hardware_addr.htype == packet -> raw -> htype && + !memcmp (lease -> hardware_addr.haddr, + packet -> raw -> chaddr, + packet -> raw -> hlen)))) { + ack_lease (packet, lease, DHCPACK, 0); + return; + } + + /* At this point, the client has requested a lease, and it's + available, but it wasn't assigned to the client, which + means that the client probably hasn't gone through the + DHCPDISCOVER part of the protocol. We are within our + rights to send a DHCPNAK. We can also send a DHCPACK. + The thing we probably should not do is to remain silent. + For now, we'll just assign the lease to the client anyway. */ + if (lease) + ack_lease (packet, lease, DHCPACK, 0); +} + +void dhcprelease (packet) + struct packet *packet; +{ + struct lease *lease; + struct iaddr cip; + int i; + + /* DHCPRELEASE must not specify address in requested-address + option, but old protocol specs weren't explicit about this, + so let it go. */ + if (packet -> options [DHO_DHCP_REQUESTED_ADDRESS].len) { + note ("DHCPRELEASE from %s specified requested-address.", + print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr)); + } + + i = DHO_DHCP_CLIENT_IDENTIFIER; + if (packet -> options [i].len) { + lease = find_lease_by_uid (packet -> options [i].data, + packet -> options [i].len); + + /* See if we can find a lease that matches the IP address + the client is claiming. */ + for (; lease; lease = lease -> n_uid) { + if (!memcmp (&packet -> raw -> ciaddr, + lease -> ip_addr.iabuf, 4)) { + break; + } + } + } else { + /* The client is supposed to pass a valid client-identifier, + but the spec on this has changed historically, so try the + IP address in ciaddr if the client-identifier fails. */ + cip.len = 4; + memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4); + lease = find_lease_by_ip_addr (cip); + } + + note ("DHCPRELEASE of %s from %s via %s (%sfound)", + inet_ntoa (packet -> raw -> ciaddr), + 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, + lease ? "" : "not "); + + /* If we found a lease, release it. */ + if (lease && lease -> ends > cur_time) { + /* first, we ping this lease to see if it's still + * there. if it is, we don't release it. + * this avoids the problem of spoofed releases + * being used to liberate addresses from the + * server. + */ + if (! lease->releasing) { + note ("DHCPRELEASE of %s from %s via %s (found)", + inet_ntoa (packet -> raw -> ciaddr), + 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); + + lease->releasing = 1; + add_timeout (cur_time + 1, lease_ping_timeout, lease); + icmp_echorequest (&(lease -> ip_addr)); + ++outstanding_pings; + } + else { + note ("DHCPRELEASE of %s from %s via %s ignored (release already pending)", + inet_ntoa (packet -> raw -> ciaddr), + 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); + } + } + else { + note ("DHCPRELEASE of %s from %s via %s for nonexistent lease", + inet_ntoa (packet -> raw -> ciaddr), + 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); + } +} + +void dhcpdecline (packet) + struct packet *packet; +{ + struct lease *lease; + struct iaddr cip; + + /* DHCPDECLINE must specify address. */ + if (!packet -> options [DHO_DHCP_REQUESTED_ADDRESS].len) { + return; + } + + cip.len = 4; + memcpy (cip.iabuf, + packet -> options [DHO_DHCP_REQUESTED_ADDRESS].data, 4); + lease = find_lease_by_ip_addr (cip); + + note ("DHCPDECLINE on %s from %s via %s", + piaddr (cip), + 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 we found a lease, mark it as unusable and complain. */ + if (lease) { + abandon_lease (lease, "declined."); + } +} + +void dhcpinform (packet) + struct packet *packet; +{ + note ("DHCPINFORM from %s", + inet_ntoa (packet -> raw -> ciaddr)); +} + +void nak_lease (packet, cip) + struct packet *packet; + struct iaddr *cip; +{ + struct sockaddr_in to; + struct in_addr from; + int result; + struct dhcp_packet raw; + unsigned char nak = DHCPNAK; + struct packet outgoing; + struct hardware hto; + int i; + + struct tree_cache *options [256]; + struct tree_cache dhcpnak_tree; + struct tree_cache dhcpmsg_tree; + + memset (options, 0, sizeof options); + memset (&outgoing, 0, sizeof outgoing); + memset (&raw, 0, sizeof raw); + outgoing.raw = &raw; + + /* Set DHCP_MESSAGE_TYPE to DHCPNAK */ + options [DHO_DHCP_MESSAGE_TYPE] = &dhcpnak_tree; + options [DHO_DHCP_MESSAGE_TYPE] -> value = &nak; + options [DHO_DHCP_MESSAGE_TYPE] -> len = sizeof nak; + options [DHO_DHCP_MESSAGE_TYPE] -> buf_size = sizeof nak; + options [DHO_DHCP_MESSAGE_TYPE] -> timeout = 0xFFFFFFFF; + options [DHO_DHCP_MESSAGE_TYPE] -> tree = (struct tree *)0; + + /* Set DHCP_MESSAGE to whatever the message is */ + options [DHO_DHCP_MESSAGE] = &dhcpmsg_tree; + options [DHO_DHCP_MESSAGE] -> value = (unsigned char *)dhcp_message; + options [DHO_DHCP_MESSAGE] -> len = + options [DHO_DHCP_MESSAGE] -> buf_size = strlen (dhcp_message); + options [DHO_DHCP_MESSAGE] -> timeout = 0xFFFFFFFF; + options [DHO_DHCP_MESSAGE] -> tree = (struct tree *)0; + + /* Do not use the client's requested parameter list. */ + i = DHO_DHCP_PARAMETER_REQUEST_LIST; + if (packet -> options [i].data) { + packet -> options [i].len = 0; + dfree (packet -> options [i].data, "nak_lease"); + packet -> options [i].data = (unsigned char *)0; + } + + /* Set up the option buffer... */ + outgoing.packet_length = + cons_options (packet, outgoing.raw, 0, options, 0, 0, 0, + (u_int8_t *)0, 0); + +/* memset (&raw.ciaddr, 0, sizeof raw.ciaddr);*/ + raw.siaddr = packet -> interface -> primary_address; + raw.giaddr = packet -> raw -> giaddr; + memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr); + raw.hlen = packet -> raw -> hlen; + raw.htype = packet -> raw -> htype; + + raw.xid = packet -> raw -> xid; + raw.secs = packet -> raw -> secs; + raw.flags = packet -> raw -> flags | htons (BOOTP_BROADCAST); + raw.hops = packet -> raw -> hops; + raw.op = BOOTREPLY; + + /* Report what we're sending... */ + note ("DHCPNAK on %s to %s via %s", + piaddr (*cip), + 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); + + + +#ifdef DEBUG_PACKET + dump_packet (packet); + dump_raw ((unsigned char *)packet -> raw, packet -> packet_length); + dump_packet (&outgoing); + dump_raw ((unsigned char *)&raw, outgoing.packet_length); +#endif + + hto.htype = packet -> raw -> htype; + hto.hlen = packet -> raw -> hlen; + memcpy (hto.haddr, packet -> raw -> chaddr, hto.hlen); + + /* Set up the common stuff... */ + memset (&to, 0, sizeof to); + to.sin_family = AF_INET; + to.sin_len = sizeof to; + + from = packet -> interface -> primary_address; + + /* Make sure that the packet is at least as big as a BOOTP packet. */ + if (outgoing.packet_length < BOOTP_MIN_LEN) + outgoing.packet_length = BOOTP_MIN_LEN; + + /* If this was gatewayed, send it back to the gateway. + Otherwise, broadcast it on the local network. */ + if (raw.giaddr.s_addr) { + to.sin_addr = raw.giaddr; + to.sin_port = local_port; + + if (fallback_interface) { + result = send_packet (fallback_interface, + packet, &raw, + outgoing.packet_length, + from, &to, &hto); + if (result == -1) + warn ("send_fallback: %m"); + return; + } + } else { + to.sin_addr.s_addr = htonl (INADDR_BROADCAST); + to.sin_port = remote_port; + } + + errno = 0; + result = send_packet (packet -> interface, + packet, &raw, outgoing.packet_length, + from, &to, (struct hardware *)0); +} + +void ack_lease (packet, lease, offer, when) + struct packet *packet; + struct lease *lease; + unsigned int offer; + TIME when; +{ + struct lease lt; + struct lease_state *state; + TIME lease_time; + TIME offered_lease_time; + TIME max_lease_time; + TIME default_lease_time; + int ulafdr; + + struct class *vendor_class, *user_class; + int i; + + /* If we're already acking this lease, don't do it again. */ + if (lease -> state) { + note ("already acking lease %s", piaddr (lease -> ip_addr)); + return; + } + + if (packet -> options [DHO_DHCP_CLASS_IDENTIFIER].len) { + vendor_class = + find_class (0, + packet -> + options [DHO_DHCP_CLASS_IDENTIFIER].data, + packet -> + options [DHO_DHCP_CLASS_IDENTIFIER].len); + } else { + vendor_class = (struct class *)0; + } + + if (packet -> options [DHO_DHCP_USER_CLASS_ID].len) { + user_class = + find_class (1, + packet -> + options [DHO_DHCP_USER_CLASS_ID].data, + packet -> + options [DHO_DHCP_USER_CLASS_ID].len); + } else { + user_class = (struct class *)0; + } + + /* + * If there is not a specific host entry, and either the + * vendor class or user class (if they exist) deny booting, + * then bug out. + */ + if (!lease -> host) { + if (vendor_class && !vendor_class -> group -> allow_booting) { + debug ("Booting denied by vendor class"); + return; + } + + if (user_class && !user_class -> group -> allow_booting) { + debug ("Booting denied by user class"); + return; + } + } + + /* Allocate a lease state structure... */ + state = new_lease_state ("ack_lease"); + if (!state) + error ("unable to allocate lease state!"); + memset (state, 0, sizeof *state); + state -> got_requested_address = packet -> got_requested_address; + state -> shared_network = packet -> interface -> shared_network; + + /* Remember if we got a server identifier option. */ + if (packet -> options [DHO_DHCP_SERVER_IDENTIFIER].len) + state -> got_server_identifier = 1; + + /* Replace the old lease hostname with the new one, if it's changed. */ + if (packet -> options [DHO_HOST_NAME].len && + lease -> client_hostname && + (strlen (lease -> client_hostname) == + packet -> options [DHO_HOST_NAME].len) && + !memcmp (lease -> client_hostname, + packet -> options [DHO_HOST_NAME].data, + packet -> options [DHO_HOST_NAME].len)) { + } else if (packet -> options [DHO_HOST_NAME].len) { + if (lease -> client_hostname) + free (lease -> client_hostname); + lease -> client_hostname = + malloc (packet -> options [DHO_HOST_NAME].len + 1); + if (!lease -> client_hostname) + error ("no memory for client hostname.\n"); + memcpy (lease -> client_hostname, + packet -> options [DHO_HOST_NAME].data, + packet -> options [DHO_HOST_NAME].len); + lease -> client_hostname + [packet -> options [DHO_HOST_NAME].len] = 0; + } else if (lease -> client_hostname) { + free (lease -> client_hostname); + lease -> client_hostname = 0; + } + + /* Choose a filename; first from the host_decl, if any, then from + the user class, then from the vendor class. */ + if (lease -> host && lease -> host -> group -> filename) + strlcpy (state -> filename, lease -> host -> group -> filename, + sizeof state -> filename); + else if (user_class && user_class -> group -> filename) + strlcpy (state -> filename, user_class -> group -> filename, + sizeof state -> filename); + else if (vendor_class && vendor_class -> group -> filename) + strlcpy (state -> filename, vendor_class -> group -> filename, + sizeof state -> filename); + else if (packet -> raw -> file [0]) + strlcpy (state -> filename, packet -> raw -> file, + sizeof state -> filename); + else if (lease -> subnet -> group -> filename) + strlcpy (state -> filename, + lease -> subnet -> group -> filename, + sizeof state -> filename); + else + strlcpy (state -> filename, "", sizeof state -> filename); + + /* Choose a server name as above. */ + if (lease -> host && lease -> host -> group -> server_name) + state -> server_name = lease -> host -> group -> server_name; + else if (user_class && user_class -> group -> server_name) + state -> server_name = user_class -> group -> server_name; + else if (vendor_class && vendor_class -> group -> server_name) + state -> server_name = vendor_class -> group -> server_name; + else if (lease -> subnet -> group -> server_name) + state -> server_name = + lease -> subnet -> group -> server_name; + else state -> server_name = (char *)0; + + /* At this point, we have a lease that we can offer the client. + Now we construct a lease structure that contains what we want, + and call supersede_lease to do the right thing with it. */ + + memset (<, 0, sizeof lt); + + /* Use the ip address of the lease that we finally found in + the database. */ + lt.ip_addr = lease -> ip_addr; + + /* Start now. */ + lt.starts = cur_time; + + /* Figure out maximum lease time. */ + if (lease -> host && + lease -> host -> group -> max_lease_time) + max_lease_time = lease -> host -> group -> max_lease_time; + else + max_lease_time = lease -> subnet -> group -> max_lease_time; + + /* Figure out default lease time. */ + if (lease -> host + && lease -> host -> group -> default_lease_time) + default_lease_time = + lease -> host -> group -> default_lease_time; + else + default_lease_time = + lease -> subnet -> group -> default_lease_time; + + /* Figure out how long a lease to assign. If this is a + dynamic BOOTP lease, its duration must be infinite. */ + if (offer) { + if (packet -> options [DHO_DHCP_LEASE_TIME].len == 4) { + lease_time = getULong + (packet -> options [DHO_DHCP_LEASE_TIME].data); + + /* Don't let the client ask for a longer lease than + is supported for this subnet or host. */ + if (lease_time > max_lease_time) + lease_time = max_lease_time; + } else + lease_time = default_lease_time; + + state -> offered_expiry = cur_time + lease_time; + if (when) + lt.ends = when; + else + lt.ends = state -> offered_expiry; + } else { + if (lease -> host && + lease -> host -> group -> bootp_lease_length) + lt.ends = (cur_time + + lease -> host -> + group -> bootp_lease_length); + else if (lease -> subnet -> group -> bootp_lease_length) + lt.ends = (cur_time + + lease -> subnet -> + group -> bootp_lease_length); + else if (lease -> host && + lease -> host -> group -> bootp_lease_cutoff) + lt.ends = lease -> host -> group -> bootp_lease_cutoff; + else + lt.ends = (lease -> subnet -> + group -> bootp_lease_cutoff); + state -> offered_expiry = lt.ends; + lt.flags = BOOTP_LEASE; + } + + /* Record the uid, if given... */ + i = DHO_DHCP_CLIENT_IDENTIFIER; + if (packet -> options [i].len) { + if (packet -> options [i].len <= sizeof lt.uid_buf) { + memcpy (lt.uid_buf, packet -> options [i].data, + packet -> options [i].len); + lt.uid = lt.uid_buf; + lt.uid_max = sizeof lt.uid_buf; + lt.uid_len = packet -> options [i].len; + } else { + lt.uid_max = lt.uid_len = packet -> options [i].len; + lt.uid = (unsigned char *)malloc (lt.uid_max); + if (!lt.uid) + error ("can't allocate memory for large uid."); + memcpy (lt.uid, + packet -> options [i].data, lt.uid_len); + } + } + + lt.host = lease -> host; + lt.subnet = lease -> subnet; + lt.shared_network = lease -> shared_network; + + /* Don't call supersede_lease on a mocked-up lease. */ + if (lease -> flags & STATIC_LEASE) { + /* Copy the hardware address into the static lease + structure. */ + lease -> hardware_addr.hlen = packet -> raw -> hlen; + lease -> hardware_addr.htype = packet -> raw -> htype; + memcpy (lease -> hardware_addr.haddr, packet -> raw -> chaddr, + sizeof packet -> raw -> chaddr); /* XXX */ + } else { + /* Record the hardware address, if given... */ + lt.hardware_addr.hlen = packet -> raw -> hlen; + lt.hardware_addr.htype = packet -> raw -> htype; + memcpy (lt.hardware_addr.haddr, packet -> raw -> chaddr, + sizeof packet -> raw -> chaddr); + + /* Install the new information about this lease in the + database. If this is a DHCPACK or a dynamic BOOTREPLY + and we can't write the lease, don't ACK it (or BOOTREPLY + it) either. */ + + if (!(supersede_lease (lease, <, !offer || offer == DHCPACK) + || (offer && offer != DHCPACK))) + return; + } + + /* Remember the interface on which the packet arrived. */ + state -> ip = packet -> interface; + + /* Set a flag if this client is a lame Microsoft client that NUL + terminates string options and expects us to do likewise. */ + if (packet -> options [DHO_HOST_NAME].data && + packet -> options [DHO_HOST_NAME].data + [packet -> options [DHO_HOST_NAME].len - 1] == '\0') + lease -> flags |= MS_NULL_TERMINATION; + else + lease -> flags &= ~MS_NULL_TERMINATION; + + /* Remember the giaddr, xid, secs, flags and hops. */ + state -> giaddr = packet -> raw -> giaddr; + state -> ciaddr = packet -> raw -> ciaddr; + state -> xid = packet -> raw -> xid; + state -> secs = packet -> raw -> secs; + state -> bootp_flags = packet -> raw -> flags; + state -> hops = packet -> raw -> hops; + state -> offer = offer; + + /* Figure out what options to send to the client: */ + + /* Start out with the subnet options... */ + memcpy (state -> options, + lease -> subnet -> group -> options, + sizeof state -> options); + + /* Vendor and user classes are only supported for DHCP clients. */ + if (state -> offer) { + /* If we have a vendor class, install those options, + superseding any subnet options. */ + if (vendor_class) { + for (i = 0; i < 256; i++) + if (vendor_class -> group -> options [i]) + state -> options [i] = + (vendor_class -> group -> + options [i]); + } + + /* If we have a user class, install those options, + superseding any subnet and vendor class options. */ + if (user_class) { + for (i = 0; i < 256; i++) + if (user_class -> group -> options [i]) + state -> options [i] = + (user_class -> group -> + options [i]); + } + + } + + /* If we have a host_decl structure, install the associated + options, superseding anything that's in the way. */ + if (lease -> host) { + for (i = 0; i < 256; i++) + if (lease -> host -> group -> options [i]) + state -> options [i] = (lease -> host -> + group -> options [i]); + } + + /* Get the Maximum Message Size option from the packet, if one + was sent. */ + i = DHO_DHCP_MAX_MESSAGE_SIZE; + if (packet -> options [i].data && + (packet -> options [i].len == sizeof (u_int16_t))) + state -> max_message_size = + getUShort (packet -> options [i].data); + /* Otherwise, if a maximum message size was specified, use that. */ + else if (state -> options [i] && state -> options [i] -> value) + state -> max_message_size = + getUShort (state -> options [i] -> value); + + /* Save the parameter request list if there is one. */ + i = DHO_DHCP_PARAMETER_REQUEST_LIST; + if (packet -> options [i].data) { + state -> prl = dmalloc (packet -> options [i].len, + "ack_lease: prl"); + if (!state -> prl) + warn ("no memory for parameter request list"); + else { + memcpy (state -> prl, + packet -> options [i].data, + packet -> options [i].len); + state -> prl_len = packet -> options [i].len; + } + } + + /* If we didn't get a hostname from an option somewhere, see if + we can get one from the lease. */ + i = DHO_HOST_NAME; + if (!state -> options [i] && lease -> hostname) { + state -> options [i] = new_tree_cache ("hostname"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = + (unsigned char *)lease -> hostname; + state -> options [i] -> len = strlen (lease -> hostname); + state -> options [i] -> buf_size = state -> options [i] -> len; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + } + + /* Now, if appropriate, put in DHCP-specific options that + override those. */ + if (state -> offer) { + i = DHO_DHCP_MESSAGE_TYPE; + state -> options [i] = new_tree_cache ("message-type"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = &state -> offer; + state -> options [i] -> len = sizeof state -> offer; + state -> options [i] -> buf_size = sizeof state -> offer; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + + i = DHO_DHCP_SERVER_IDENTIFIER; + if (!state -> options [i]) { + use_primary: + state -> options [i] = new_tree_cache ("server-id"); + state -> options [i] -> value = + (unsigned char *)&state -> + ip -> primary_address; + state -> options [i] -> len = + sizeof state -> ip -> primary_address; + state -> options [i] -> buf_size + = state -> options [i] -> len; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + state -> from.len = + sizeof state -> ip -> primary_address; + memcpy (state -> from.iabuf, + &state -> ip -> primary_address, + state -> from.len); + } else { + /* Find the value of the server identifier... */ + if (!tree_evaluate (state -> options [i])) + goto use_primary; + if (!state -> options [i] -> value || + (state -> options [i] -> len > + sizeof state -> from.iabuf)) + goto use_primary; + + state -> from.len = state -> options [i] -> len; + memcpy (state -> from.iabuf, + state -> options [i] -> value, + state -> from.len); + } + + /* Sanity check the lease time. */ + if ((state -> offered_expiry - cur_time) < 15) + offered_lease_time = default_lease_time; + else if (state -> offered_expiry - cur_time > max_lease_time) + offered_lease_time = max_lease_time; + else + offered_lease_time = + state -> offered_expiry - cur_time; + + putULong ((unsigned char *)&state -> expiry, + offered_lease_time); + i = DHO_DHCP_LEASE_TIME; + state -> options [i] = new_tree_cache ("lease-expiry"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = + (unsigned char *)&state -> expiry; + state -> options [i] -> len = sizeof state -> expiry; + state -> options [i] -> buf_size = sizeof state -> expiry; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + + /* Renewal time is lease time * 0.5. */ + offered_lease_time /= 2; + putULong ((unsigned char *)&state -> renewal, + offered_lease_time); + i = DHO_DHCP_RENEWAL_TIME; + state -> options [i] = new_tree_cache ("renewal-time"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = + (unsigned char *)&state -> renewal; + state -> options [i] -> len = sizeof state -> renewal; + state -> options [i] -> buf_size = sizeof state -> renewal; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + + + /* Rebinding time is lease time * 0.875. */ + offered_lease_time += (offered_lease_time / 2 + + offered_lease_time / 4); + putULong ((unsigned char *)&state -> rebind, + offered_lease_time); + i = DHO_DHCP_REBINDING_TIME; + state -> options [i] = new_tree_cache ("rebind-time"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = + (unsigned char *)&state -> rebind; + state -> options [i] -> len = sizeof state -> rebind; + state -> options [i] -> buf_size = sizeof state -> rebind; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + + /* If we used the vendor class the client specified, we + have to return it. */ + if (vendor_class) { + i = DHO_DHCP_CLASS_IDENTIFIER; + state -> options [i] = + new_tree_cache ("class-identifier"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = + (unsigned char *)vendor_class -> name; + state -> options [i] -> len = + strlen (vendor_class -> name); + state -> options [i] -> buf_size = + state -> options [i] -> len; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + } + + /* If we used the user class the client specified, we + have to return it. */ + if (user_class) { + i = DHO_DHCP_USER_CLASS_ID; + state -> options [i] = new_tree_cache ("user-class"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = + (unsigned char *)user_class -> name; + state -> options [i] -> len = + strlen (user_class -> name); + state -> options [i] -> buf_size = + state -> options [i] -> len; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + } + } + + /* Use the subnet mask from the subnet declaration if no other + mask has been provided. */ + i = DHO_SUBNET_MASK; + if (!state -> options [i]) { + state -> options [i] = new_tree_cache ("subnet-mask"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = + lease -> subnet -> netmask.iabuf; + state -> options [i] -> len = lease -> subnet -> netmask.len; + state -> options [i] -> buf_size = + lease -> subnet -> netmask.len; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + } + + /* If so directed, use the leased IP address as the router address. + This supposedly makes Win95 machines ARP for all IP addresses, + so if the local router does proxy arp, you win. */ + + ulafdr = 0; + if (lease -> host) { + if (lease -> host -> group -> use_lease_addr_for_default_route) + ulafdr = 1; + } else if (user_class) { + if (user_class -> group -> use_lease_addr_for_default_route) + ulafdr = 1; + } else if (vendor_class) { + if (vendor_class -> group -> use_lease_addr_for_default_route) + ulafdr = 1; + } else if (lease -> subnet -> group -> + use_lease_addr_for_default_route) + ulafdr = 1; + else + ulafdr = 0; + + i = DHO_ROUTERS; + if (ulafdr && !state -> options [i]) { + state -> options [i] = new_tree_cache ("routers"); + state -> options [i] -> flags = TC_TEMPORARY; + state -> options [i] -> value = + lease -> ip_addr.iabuf; + state -> options [i] -> len = + lease -> ip_addr.len; + state -> options [i] -> buf_size = + lease -> ip_addr.len; + state -> options [i] -> timeout = 0xFFFFFFFF; + state -> options [i] -> tree = (struct tree *)0; + } + +#ifdef DEBUG_PACKET + dump_packet (packet); + dump_raw ((unsigned char *)packet -> raw, packet -> packet_length); +#endif + + lease -> state = state; + + /* If this is a DHCPOFFER, ping the lease address before actually + sending the offer. */ + if (offer == DHCPOFFER && !(lease -> flags & STATIC_LEASE) && + cur_time - lease -> timestamp > 60) { + lease -> timestamp = cur_time; + icmp_echorequest (&lease -> ip_addr); + add_timeout (cur_time + 1, lease_ping_timeout, lease); + ++outstanding_pings; + } else { + lease -> timestamp = cur_time; + dhcp_reply (lease); + } +} + +void dhcp_reply (lease) + struct lease *lease; +{ + int bufs = 0; + int packet_length; + struct dhcp_packet raw; + struct sockaddr_in to; + struct in_addr from; + struct hardware hto; + int result; + int i; + struct lease_state *state = lease -> state; + int nulltp, bootpp; + u_int8_t *prl; + int prl_len; + + if (!state) + error ("dhcp_reply was supplied lease with no state!"); + + /* Compose a response for the client... */ + memset (&raw, 0, sizeof raw); + + /* Copy in the filename if given; otherwise, flag the filename + buffer as available for options. */ + if (state -> filename [0]) + strlcpy (raw.file, state -> filename, sizeof raw.file); + else + bufs |= 1; + + /* Copy in the server name if given; otherwise, flag the + server_name buffer as available for options. */ + if (state -> server_name) + strlcpy (raw.sname, state -> server_name, sizeof raw.sname); + else + bufs |= 2; /* XXX */ + + memcpy (raw.chaddr, lease -> hardware_addr.haddr, sizeof raw.chaddr); + raw.hlen = lease -> hardware_addr.hlen; + raw.htype = lease -> hardware_addr.htype; + + /* See if this is a Microsoft client that NUL-terminates its + strings and expects us to do likewise... */ + if (lease -> flags & MS_NULL_TERMINATION) + nulltp = 1; + else + nulltp = 0; + + /* See if this is a bootp client... */ + if (state -> offer) + bootpp = 0; + else + bootpp = 1; + + if (state -> options [DHO_DHCP_PARAMETER_REQUEST_LIST] && + state -> options [DHO_DHCP_PARAMETER_REQUEST_LIST] -> value) { + prl = state -> options + [DHO_DHCP_PARAMETER_REQUEST_LIST] -> value; + prl_len = state -> options + [DHO_DHCP_PARAMETER_REQUEST_LIST] -> len; + } else if (state -> prl) { + prl = state -> prl; + prl_len = state -> prl_len; + } else { + prl = (u_int8_t *)0; + prl_len = 0; + } + + /* Insert such options as will fit into the buffer. */ + packet_length = cons_options ((struct packet *)0, &raw, + state -> max_message_size, + state -> options, + bufs, nulltp, bootpp, prl, prl_len); + + /* Having done the cons_options(), we can release the tree_cache + entries. */ + for (i = 0; i < 256; i++) { + if (state -> options [i] && + state -> options [i] -> flags & TC_TEMPORARY) + free_tree_cache (state -> options [i], "dhcp_reply"); + } + + memcpy (&raw.ciaddr, &state -> ciaddr, sizeof raw.ciaddr); + memcpy (&raw.yiaddr, lease -> ip_addr.iabuf, 4); + + /* Figure out the address of the next server. */ + if (lease -> host && lease -> host -> group -> next_server.len) + memcpy (&raw.siaddr, + lease -> host -> group -> next_server.iabuf, 4); + else if (lease -> subnet -> group -> next_server.len) + memcpy (&raw.siaddr, + lease -> subnet -> group -> next_server.iabuf, 4); + else if (lease -> subnet -> interface_address.len) + memcpy (&raw.siaddr, + lease -> subnet -> interface_address.iabuf, 4); + else + raw.siaddr = state -> ip -> primary_address; + + raw.giaddr = state -> giaddr; + + raw.xid = state -> xid; + raw.secs = state -> secs; + raw.flags = state -> bootp_flags; + raw.hops = state -> hops; + raw.op = BOOTREPLY; + + /* Say what we're doing... */ + note ("%s on %s to %s via %s", + (state -> offer + ? (state -> offer == DHCPACK ? "DHCPACK" : "DHCPOFFER") + : "BOOTREPLY"), + piaddr (lease -> ip_addr), + print_hw_addr (lease -> hardware_addr.htype, + lease -> hardware_addr.hlen, + lease -> hardware_addr.haddr), + state -> giaddr.s_addr + ? inet_ntoa (state -> giaddr) + : state -> ip -> name); + + /* Set up the hardware address... */ + hto.htype = lease -> hardware_addr.htype; + hto.hlen = lease -> hardware_addr.hlen; + memcpy (hto.haddr, lease -> hardware_addr.haddr, hto.hlen); + + memset (&to, 0, sizeof to); + to.sin_family = AF_INET; +#ifdef HAVE_SA_LEN + to.sin_len = sizeof to; +#endif + +#ifdef DEBUG_PACKET + dump_raw ((unsigned char *)&raw, packet_length); +#endif + + /* Make sure outgoing packets are at least as big + as a BOOTP packet. */ + if (packet_length < BOOTP_MIN_LEN) + packet_length = BOOTP_MIN_LEN; + + /* 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, packet_length, + raw.siaddr, + &to, (struct hardware *)0); + + free_lease_state (state, "dhcp_reply fallback 1"); + lease -> state = (struct lease_state *)0; + return; + } + + /* If the client is RENEWING, unicast to the client using the + regular IP stack. Some clients, particularly those that + follow RFC1541, are buggy, and send both ciaddr and + server-identifier. We deal with this situation by assuming + that if we got both dhcp-server-identifier and ciaddr, and + giaddr was not set, then the client is on the local + network, and we can therefore unicast or broadcast to it + successfully. A client in REQUESTING state on another + network that's making this mistake will have set giaddr, + and will therefore get a relayed response from the above + code. */ + } else if (raw.ciaddr.s_addr && + !((state -> got_server_identifier || + (raw.flags & htons (BOOTP_BROADCAST))) && + /* XXX This won't work if giaddr isn't zero, but it is: */ + (state -> shared_network == lease -> shared_network)) && + state -> offer == DHCPACK) { + to.sin_addr = raw.ciaddr; + to.sin_port = remote_port; + + if (fallback_interface) { + result = send_packet (fallback_interface, + (struct packet *)0, + &raw, packet_length, + raw.siaddr, &to, + (struct hardware *)0); + free_lease_state (state, + "dhcp_reply fallback 2"); + lease -> state = (struct lease_state *)0; + 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))) { + to.sin_addr = raw.yiaddr; + to.sin_port = remote_port; + + /* Otherwise, broadcast it on the local network. */ + } else { + to.sin_addr.s_addr = htonl (INADDR_BROADCAST); + to.sin_port = remote_port; + } + + memcpy (&from, state -> from.iabuf, sizeof from); + + result = send_packet (state -> ip, + (struct packet *)0, &raw, packet_length, + from, &to, &hto); + + free_lease_state (state, "dhcp_reply"); + lease -> state = (struct lease_state *)0; +} + +struct lease *find_lease (packet, share, ours) + struct packet *packet; + struct shared_network *share; + int *ours; +{ + struct lease *uid_lease, *ip_lease, *hw_lease; + struct lease *lease = (struct lease *)0; + struct iaddr cip; + struct host_decl *hp, *host = (struct host_decl *)0; + struct lease *fixed_lease; + + /* Figure out what IP address the client is requesting, if any. */ + if (packet -> options [DHO_DHCP_REQUESTED_ADDRESS].len && + packet -> options [DHO_DHCP_REQUESTED_ADDRESS].len == 4) { + packet -> got_requested_address = 1; + cip.len = 4; + memcpy (cip.iabuf, + packet -> options [DHO_DHCP_REQUESTED_ADDRESS].data, + cip.len); + } else if (packet -> raw -> ciaddr.s_addr) { + cip.len = 4; + memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4); + } else + cip.len = 0; + + /* Try to find a host or lease that's been assigned to the + specified unique client identifier. */ + if (packet -> options [DHO_DHCP_CLIENT_IDENTIFIER].len) { + /* First, try to find a fixed host entry for the specified + client identifier... */ + hp = find_hosts_by_uid (packet -> options + [DHO_DHCP_CLIENT_IDENTIFIER].data, + packet -> options + [DHO_DHCP_CLIENT_IDENTIFIER].len); + if (hp) { + host = hp; + fixed_lease = mockup_lease (packet, share, hp); + uid_lease = (struct lease *)0; + } else { + uid_lease = find_lease_by_uid + (packet -> options + [DHO_DHCP_CLIENT_IDENTIFIER].data, + packet -> options + [DHO_DHCP_CLIENT_IDENTIFIER].len); + /* Find the lease matching this uid that's on the + network the packet came from (if any). */ + for (; uid_lease; uid_lease = uid_lease -> n_uid) + if (uid_lease -> shared_network == share) + break; + fixed_lease = (struct lease *)0; + if (uid_lease && + (uid_lease -> flags & ABANDONED_LEASE)) + uid_lease = (struct lease *)0; + } + } else { + uid_lease = (struct lease *)0; + fixed_lease = (struct lease *)0; + } + + /* If we didn't find a fixed lease using the uid, try doing + it with the hardware address... */ + if (!fixed_lease) { + hp = find_hosts_by_haddr (packet -> raw -> htype, + packet -> raw -> chaddr, + packet -> raw -> hlen); + if (hp) { + host = hp; /* Save it for later. */ + fixed_lease = mockup_lease (packet, share, hp); + } + } + + /* If fixed_lease is present but does not match the requested + IP address, and this is a DHCPREQUEST, then we can't return + any other lease, so we might as well return now. */ + if (packet -> packet_type == DHCPREQUEST && fixed_lease && + (fixed_lease -> ip_addr.len != cip.len || + memcmp (fixed_lease -> ip_addr.iabuf, + cip.iabuf, cip.len))) { + if (ours) + *ours = 1; + strlcpy (dhcp_message, "requested address is incorrect", + sizeof (dhcp_message)); + return (struct lease *)0; + } + + /* Try to find a lease that's been attached to the client's + hardware address... */ + hw_lease = find_lease_by_hw_addr (packet -> raw -> chaddr, + packet -> raw -> hlen); + /* Find the lease that's on the network the packet came from + (if any). */ + for (; hw_lease; hw_lease = hw_lease -> n_hw) { + if (hw_lease -> shared_network == share) { + if ((hw_lease -> flags & ABANDONED_LEASE)) + continue; + if (packet -> packet_type) + break; + if (hw_lease -> flags & + (BOOTP_LEASE | DYNAMIC_BOOTP_OK)) + break; + } + } + + /* Try to find a lease that's been allocated to the client's + IP address. */ + if (cip.len) + ip_lease = find_lease_by_ip_addr (cip); + else + ip_lease = (struct lease *)0; + + /* If ip_lease is valid at this point, set ours to one, so that + even if we choose a different lease, we know that the address + the client was requesting was ours, and thus we can NAK it. */ + if (ip_lease && ours) + *ours = 1; + + /* If the requested IP address isn't on the network the packet + came from, don't use it. Allow abandoned leases to be matched + here - if the client is requesting it, there's a decent chance + that it's because the lease database got trashed and a client + that thought it had this lease answered an ARP or PING, causing the + lease to be abandoned. If so, this request probably came from + that client. */ + if (ip_lease && (ip_lease -> shared_network != share)) { + ip_lease = (struct lease *)0; + strlcpy (dhcp_message, "requested address on bad subnet", + sizeof(dhcp_message)); + } + + /* Toss ip_lease if it hasn't yet expired and isn't owned by the + client. */ + if (ip_lease && + ip_lease -> ends >= cur_time && + ip_lease != uid_lease) { + int i = DHO_DHCP_CLIENT_IDENTIFIER; + /* Make sure that ip_lease actually belongs to the client, + and toss it if not. */ + if ((ip_lease -> uid_len && + packet -> options [i].data && + ip_lease -> uid_len == packet -> options [i].len && + !memcmp (packet -> options [i].data, + ip_lease -> uid, ip_lease -> uid_len)) || + (!ip_lease -> uid_len && + (ip_lease -> hardware_addr.htype == + packet -> raw -> htype) && + ip_lease -> hardware_addr.hlen == packet -> raw -> hlen && + !memcmp (ip_lease -> hardware_addr.haddr, + packet -> raw -> chaddr, + ip_lease -> hardware_addr.hlen))) { + if (uid_lease) { + if (uid_lease -> ends > cur_time) { + warn ("client %s has duplicate leases on %s", + print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr), + ip_lease -> shared_network -> name); + + if (uid_lease && + !packet -> raw -> ciaddr.s_addr) + release_lease (uid_lease); + } + uid_lease = ip_lease; + } + } else { + strlcpy (dhcp_message, + "requested address is not available", + sizeof(dhcp_message)); + ip_lease = (struct lease *)0; + } + + /* If we get to here and fixed_lease is not null, that means + that there are both a dynamic lease and a fixed-address + declaration for the same IP address. */ + if (packet -> packet_type == DHCPREQUEST && fixed_lease) { + fixed_lease = (struct lease *)0; + db_conflict: + warn ("Both dynamic and static leases present for %s.", + piaddr (cip)); + warn ("Either remove host declaration %s or remove %s", + (fixed_lease && fixed_lease -> host + ? (fixed_lease -> host -> name + ? fixed_lease -> host -> name : piaddr (cip)) + : piaddr (cip)), + piaddr (cip)); + warn ("from the dynamic address pool for %s", + share -> name); + if (fixed_lease) + ip_lease = (struct lease *)0; + strlcpy (dhcp_message, + "database conflict - call for help!", + sizeof(dhcp_message)); + } + } + + /* If we get to here with both fixed_lease and ip_lease not + null, then we have a configuration file bug. */ + if (packet -> packet_type == DHCPREQUEST && fixed_lease && ip_lease) + goto db_conflict; + + /* Toss hw_lease if it hasn't yet expired and the uid doesn't + match, except that if the hardware address matches and the + client is now doing dynamic BOOTP (and thus hasn't provided + a uid) we let the client get away with it. */ + if (hw_lease && + hw_lease -> ends >= cur_time && + hw_lease -> uid && + packet -> options [DHO_DHCP_CLIENT_IDENTIFIER].len && + hw_lease != uid_lease) + hw_lease = (struct lease *)0; + + /* Toss extra pointers to the same lease... */ + if (hw_lease == uid_lease) + hw_lease = (struct lease *)0; + if (ip_lease == hw_lease) + hw_lease = (struct lease *)0; + if (ip_lease == uid_lease) + uid_lease = (struct lease *)0; + + /* If we've already eliminated the lease, it wasn't there to + begin with. If we have come up with a matching lease, + set the message to bad network in case we have to throw it out. */ + if (!ip_lease) { + strlcpy (dhcp_message, "requested address not available", + sizeof(dhcp_message)); + } + + /* Now eliminate leases that are on the wrong network... */ + if (ip_lease && + (share != ip_lease -> shared_network)) { + if (packet -> packet_type == DHCPREQUEST) + release_lease (ip_lease); + ip_lease = (struct lease *)0; + } + if (uid_lease && + (share != uid_lease -> shared_network)) { + if (packet -> packet_type == DHCPREQUEST) + release_lease (uid_lease); + uid_lease = (struct lease *)0; + } + if (hw_lease && + (share != hw_lease -> shared_network)) { + if (packet -> packet_type == DHCPREQUEST) + release_lease (hw_lease); + hw_lease = (struct lease *)0; + } + + /* If this is a DHCPREQUEST, make sure the lease we're going to return + matches the requested IP address. If it doesn't, don't return a + lease at all. */ + if (packet -> packet_type == DHCPREQUEST && !ip_lease && !fixed_lease) + return (struct lease *)0; + + /* At this point, if fixed_lease is nonzero, we can assign it to + this client. */ + if (fixed_lease) { + lease = fixed_lease; + } + + /* If we got a lease that matched the ip address and don't have + a better offer, use that; otherwise, release it. */ + if (ip_lease) { + if (lease) { + if (packet -> packet_type == DHCPREQUEST) + release_lease (ip_lease); + } else { + lease = ip_lease; + lease -> host = (struct host_decl *)0; + } + } + + /* If we got a lease that matched the client identifier, we may want + to use it, but if we already have a lease we like, we must free + the lease that matched the client identifier. */ + if (uid_lease) { + if (lease) { + if (packet -> packet_type == DHCPREQUEST) + release_lease (uid_lease); + } else { + lease = uid_lease; + lease -> host = (struct host_decl *)0; + } + } + + /* The lease that matched the hardware address is treated likewise. */ + if (hw_lease) { + if (lease) { + if (packet -> packet_type == DHCPREQUEST) + release_lease (hw_lease); + } else { + lease = hw_lease; + lease -> host = (struct host_decl *)0; + } + } + + /* If we found a host_decl but no matching address, try to + find a host_decl that has no address, and if there is one, + hang it off the lease so that we can use the supplied + options. */ + if (lease && host && !lease -> host) { + for (; host; host = host -> n_ipaddr) { + if (!host -> fixed_addr) { + lease -> host = host; + break; + } + } + } + + /* If we find an abandoned lease, take it, but print a + warning message, so that if it continues to lose, + the administrator will eventually investigate. */ + if (lease && (lease -> flags & ABANDONED_LEASE)) { + if (packet -> packet_type == DHCPREQUEST) { + warn ("Reclaiming REQUESTed abandoned IP address %s.", + piaddr (lease -> ip_addr)); + lease -> flags &= ~ABANDONED_LEASE; + } else + lease = (struct lease *)0; + } + + return lease; +} + +/* Search the provided host_decl structure list for an address that's on + the specified shared network. If one is found, mock up and return a + lease structure for it; otherwise return the null pointer. */ + +struct lease *mockup_lease (packet, share, hp) + struct packet *packet; + struct shared_network *share; + struct host_decl *hp; +{ + static struct lease mock; + + mock.subnet = find_host_for_network (&hp, &mock.ip_addr, share); + if (!mock.subnet) + return (struct lease *)0; + mock.next = mock.prev = (struct lease *)0; + mock.shared_network = mock.subnet -> shared_network; + mock.host = hp; + + if (hp -> group -> options [DHO_DHCP_CLIENT_IDENTIFIER]) { + mock.uid = hp -> group -> + options [DHO_DHCP_CLIENT_IDENTIFIER] -> value; + mock.uid_len = hp -> group -> + options [DHO_DHCP_CLIENT_IDENTIFIER] -> len; + } else { + mock.uid = (unsigned char *)0; + mock.uid_len = 0; + } + + mock.hardware_addr = hp -> interface; + mock.starts = mock.timestamp = mock.ends = MIN_TIME; + mock.flags = STATIC_LEASE; + return &mock; +} |