diff options
Diffstat (limited to 'server/dhcp.c')
-rw-r--r-- | server/dhcp.c | 4474 |
1 files changed, 4474 insertions, 0 deletions
diff --git a/server/dhcp.c b/server/dhcp.c new file mode 100644 index 0000000..da4585f --- /dev/null +++ b/server/dhcp.c @@ -0,0 +1,4474 @@ +/* dhcp.c + + DHCP Protocol engine. */ + +/* + * Copyright (c) 2004-2011 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> +#include <limits.h> +#include <sys/time.h> + +static void commit_leases_ackout(void *foo); +static void maybe_return_agent_options(struct packet *packet, + struct option_state *options); + +int outstanding_pings; + +struct leasequeue *ackqueue_head, *ackqueue_tail; +static struct leasequeue *free_ackqueue; +static struct timeval max_fsync; + +int outstanding_acks; +int max_outstanding_acks = DEFAULT_DELAYED_ACK; +int max_ack_delay_secs = DEFAULT_ACK_DELAY_SECS; +int max_ack_delay_usecs = DEFAULT_ACK_DELAY_USECS; +int min_ack_delay_usecs = DEFAULT_MIN_ACK_DELAY_USECS; + +static char dhcp_message [256]; +static int site_code_min; + +static int find_min_site_code(struct universe *); +static isc_result_t lowest_site_code(const void *, unsigned, void *); + +static const char *dhcp_type_names [] = { + "DHCPDISCOVER", + "DHCPOFFER", + "DHCPREQUEST", + "DHCPDECLINE", + "DHCPACK", + "DHCPNAK", + "DHCPRELEASE", + "DHCPINFORM", + "type 9", + "DHCPLEASEQUERY", + "DHCPLEASEUNASSIGNED", + "DHCPLEASEUNKNOWN", + "DHCPLEASEACTIVE" +}; +const int dhcp_type_name_max = ((sizeof dhcp_type_names) / sizeof (char *)); + +#if defined (TRACING) +# define send_packet trace_packet_send +#endif + +void +dhcp (struct packet *packet) { + int ms_nulltp = 0; + struct option_cache *oc; + struct lease *lease = NULL; + const char *errmsg; + struct data_string data; + + if (!locate_network(packet) && + packet->packet_type != DHCPREQUEST && + packet->packet_type != DHCPINFORM && + packet->packet_type != DHCPLEASEQUERY) { + const char *s; + char typebuf[32]; + errmsg = "unknown network segment"; + bad_packet: + + if (packet->packet_type > 0 && + packet->packet_type <= dhcp_type_name_max) { + s = dhcp_type_names[packet->packet_type - 1]; + } else { + /* %Audit% Cannot exceed 28 bytes. %2004.06.17,Safe% */ + sprintf(typebuf, "type %d", packet->packet_type); + s = typebuf; + } + + log_info("%s from %s via %s: %s", s, + (packet->raw->htype + ? print_hw_addr(packet->raw->htype, + packet->raw->hlen, + packet->raw->chaddr) + : "<no identifier>"), + packet->raw->giaddr.s_addr + ? inet_ntoa(packet->raw->giaddr) + : packet->interface->name, errmsg); + goto out; + } + + /* There is a problem with the relay agent information option, + * which is that in order for a normal relay agent to append + * this option, the relay agent has to have been involved in + * getting the packet from the client to the server. Note + * that this is the software entity known as the relay agent, + * _not_ the hardware entity known as a router in which the + * relay agent may be running, so the fact that a router has + * forwarded a packet does not mean that the relay agent in + * the router was involved. + * + * So when the client broadcasts (DHCPDISCOVER, or giaddr is set), + * we can be sure that there are either agent options in the + * packet, or there aren't supposed to be. When the giaddr is not + * set, it's still possible that the client is on a directly + * attached subnet, and agent options are being appended by an l2 + * device that has no address, and so sets no giaddr. + * + * But in either case it's possible that the packets we receive + * from the client in RENEW state may not include the agent options, + * so if they are not in the packet we must "pretend" the last values + * we observed were provided. + */ + if (packet->packet_type == DHCPREQUEST && + packet->raw->ciaddr.s_addr && !packet->raw->giaddr.s_addr && + (packet->options->universe_count <= agent_universe.index || + packet->options->universes[agent_universe.index] == NULL)) + { + struct iaddr cip; + + cip.len = sizeof packet -> raw -> ciaddr; + memcpy (cip.iabuf, &packet -> raw -> ciaddr, + sizeof packet -> raw -> ciaddr); + if (!find_lease_by_ip_addr (&lease, cip, MDL)) + goto nolease; + + /* If there are no agent options on the lease, it's not + interesting. */ + if (!lease -> agent_options) + goto nolease; + + /* The client should not be unicasting a renewal if its lease + has expired, so make it go through the process of getting + its agent options legally. */ + if (lease -> ends < cur_time) + goto nolease; + + if (lease -> uid_len) { + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_CLIENT_IDENTIFIER); + if (!oc) + goto nolease; + + memset (&data, 0, sizeof data); + if (!evaluate_option_cache (&data, + packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, + (struct option_state *)0, + &global_scope, oc, MDL)) + goto nolease; + if (lease -> uid_len != data.len || + memcmp (lease -> uid, data.data, data.len)) { + data_string_forget (&data, MDL); + goto nolease; + } + data_string_forget (&data, MDL); + } else + if ((lease -> hardware_addr.hbuf [0] != + packet -> raw -> htype) || + (lease -> hardware_addr.hlen - 1 != + packet -> raw -> hlen) || + memcmp (&lease -> hardware_addr.hbuf [1], + packet -> raw -> chaddr, + packet -> raw -> hlen)) + goto nolease; + + /* Okay, so we found a lease that matches the client. */ + option_chain_head_reference ((struct option_chain_head **) + &(packet -> options -> universes + [agent_universe.index]), + lease -> agent_options, MDL); + + if (packet->options->universe_count <= agent_universe.index) + packet->options->universe_count = + agent_universe.index + 1; + + packet->agent_options_stashed = ISC_TRUE; + } + nolease: + + /* If a client null terminates options it sends, it probably + * expects the server to reciprocate. + */ + if ((oc = lookup_option (&dhcp_universe, packet -> options, + DHO_HOST_NAME))) { + if (!oc -> expression) + ms_nulltp = oc->flags & OPTION_HAD_NULLS; + } + + /* Classify the client. */ + classify_client (packet); + + switch (packet -> packet_type) { + case DHCPDISCOVER: + dhcpdiscover (packet, ms_nulltp); + break; + + case DHCPREQUEST: + dhcprequest (packet, ms_nulltp, lease); + break; + + case DHCPRELEASE: + dhcprelease (packet, ms_nulltp); + break; + + case DHCPDECLINE: + dhcpdecline (packet, ms_nulltp); + break; + + case DHCPINFORM: + dhcpinform (packet, ms_nulltp); + break; + + case DHCPLEASEQUERY: + dhcpleasequery(packet, ms_nulltp); + break; + + case DHCPACK: + case DHCPOFFER: + case DHCPNAK: + case DHCPLEASEUNASSIGNED: + case DHCPLEASEUNKNOWN: + case DHCPLEASEACTIVE: + break; + + default: + errmsg = "unknown packet type"; + goto bad_packet; + } + out: + if (lease) + lease_dereference (&lease, MDL); +} + +void dhcpdiscover (packet, ms_nulltp) + struct packet *packet; + int ms_nulltp; +{ + struct lease *lease = (struct lease *)0; + char msgbuf [1024]; /* XXX */ + TIME when; + const char *s; + int peer_has_leases = 0; +#if defined (FAILOVER_PROTOCOL) + dhcp_failover_state_t *peer; +#endif + + find_lease (&lease, packet, packet -> shared_network, + 0, &peer_has_leases, (struct lease *)0, MDL); + + if (lease && lease -> client_hostname) { + if ((strlen (lease -> client_hostname) <= 64) && + db_printable((unsigned char *)lease->client_hostname)) + s = lease -> client_hostname; + else + s = "Hostname Unsuitable for Printing"; + } else + s = (char *)0; + + /* %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, "DHCPDISCOVER from %s %s%s%svia %s", + (packet -> raw -> htype + ? print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr) + : (lease + ? print_hex_1(lease->uid_len, lease->uid, 60) + : "<no identifier>")), + s ? "(" : "", s ? s : "", s ? ") " : "", + packet -> raw -> giaddr.s_addr + ? inet_ntoa (packet -> raw -> giaddr) + : packet -> interface -> name); + + /* Sourceless packets don't make sense here. */ + if (!packet -> shared_network) { + log_info ("Packet from unknown subnet: %s", + inet_ntoa (packet -> raw -> giaddr)); + goto out; + } + +#if defined (FAILOVER_PROTOCOL) + if (lease && lease -> pool && lease -> pool -> failover_peer) { + peer = lease -> pool -> failover_peer; + + /* + * If the lease is ours to (re)allocate, then allocate it. + * + * If the lease is active, it belongs to the client. This + * is the right lease, if we are to offer one. We decide + * whether or not to offer later on. + * + * If the lease was last active, and we've reached this + * point, then it was last active with the same client. We + * can safely re-activate the lease with this client. + */ + if (lease->binding_state == FTS_ACTIVE || + lease->rewind_binding_state == FTS_ACTIVE || + lease_mine_to_reallocate(lease)) { + ; /* This space intentionally left blank. */ + + /* Otherwise, we can't let the client have this lease. */ + } else { +#if defined (DEBUG_FIND_LEASE) + log_debug ("discarding %s - %s", + piaddr (lease -> ip_addr), + binding_state_print (lease -> binding_state)); +#endif + lease_dereference (&lease, MDL); + } + } +#endif + + /* If we didn't find a lease, try to allocate one... */ + if (!lease) { + if (!allocate_lease (&lease, packet, + packet -> shared_network -> pools, + &peer_has_leases)) { + if (peer_has_leases) + log_error ("%s: peer holds all free leases", + msgbuf); + else + log_error ("%s: network %s: no free leases", + msgbuf, + packet -> shared_network -> name); + return; + } + } + +#if defined (FAILOVER_PROTOCOL) + if (lease && lease -> pool && lease -> pool -> failover_peer) { + peer = lease -> pool -> failover_peer; + if (peer -> service_state == not_responding || + peer -> service_state == service_startup) { + log_info ("%s: not responding%s", + msgbuf, peer -> nrr); + goto out; + } + } else + peer = (dhcp_failover_state_t *)0; + + /* Do load balancing if configured. */ + if (peer && (peer -> service_state == cooperating) && + !load_balance_mine (packet, peer)) { + if (peer_has_leases) { + log_debug ("%s: load balance to peer %s", + msgbuf, peer -> name); + goto out; + } else { + log_debug ("%s: cancel load balance to peer %s - %s", + msgbuf, peer -> name, "no free leases"); + } + } +#endif + + /* If it's an expired lease, get rid of any bindings. */ + if (lease -> ends < cur_time && lease -> scope) + binding_scope_dereference (&lease -> scope, MDL); + + /* Set the lease to really expire in 2 minutes, unless it has + not yet expired, in which case leave its expiry time alone. */ + when = cur_time + 120; + if (when < lease -> ends) + when = lease -> ends; + + ack_lease (packet, lease, DHCPOFFER, when, msgbuf, ms_nulltp, + (struct host_decl *)0); + out: + if (lease) + lease_dereference (&lease, MDL); +} + +void dhcprequest (packet, ms_nulltp, ip_lease) + struct packet *packet; + int ms_nulltp; + struct lease *ip_lease; +{ + struct lease *lease; + struct iaddr cip; + struct iaddr sip; + struct subnet *subnet; + int ours = 0; + struct option_cache *oc; + struct data_string data; + char msgbuf [1024]; /* XXX */ + const char *s; + char smbuf [19]; +#if defined (FAILOVER_PROTOCOL) + dhcp_failover_state_t *peer; +#endif + int have_server_identifier = 0; + int have_requested_addr = 0; + + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_REQUESTED_ADDRESS); + memset (&data, 0, sizeof data); + if (oc && + evaluate_option_cache (&data, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, (struct option_state *)0, + &global_scope, oc, MDL)) { + cip.len = 4; + memcpy (cip.iabuf, data.data, 4); + data_string_forget (&data, MDL); + have_requested_addr = 1; + } else { + oc = (struct option_cache *)0; + cip.len = 4; + memcpy (cip.iabuf, &packet -> raw -> ciaddr.s_addr, 4); + } + + /* Find the lease that matches the address requested by the + client. */ + + subnet = (struct subnet *)0; + lease = (struct lease *)0; + if (find_subnet (&subnet, cip, MDL)) + find_lease (&lease, packet, + subnet -> shared_network, &ours, 0, ip_lease, MDL); + + if (lease && lease -> client_hostname) { + if ((strlen (lease -> client_hostname) <= 64) && + db_printable((unsigned char *)lease->client_hostname)) + s = lease -> client_hostname; + else + s = "Hostname Unsuitable for Printing"; + } else + s = (char *)0; + + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_SERVER_IDENTIFIER); + memset (&data, 0, sizeof data); + if (oc && + evaluate_option_cache (&data, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, (struct option_state *)0, + &global_scope, oc, MDL)) { + sip.len = 4; + memcpy (sip.iabuf, data.data, 4); + data_string_forget (&data, MDL); + /* piaddr() should not return more than a 15 byte string. + * safe. + */ + sprintf (smbuf, " (%s)", piaddr (sip)); + have_server_identifier = 1; + } else + smbuf [0] = 0; + + /* %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, + "DHCPREQUEST for %s%s from %s %s%s%svia %s", + piaddr (cip), smbuf, + (packet -> raw -> htype + ? print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr) + : (lease + ? print_hex_1(lease->uid_len, lease->uid, 60) + : "<no identifier>")), + s ? "(" : "", s ? s : "", s ? ") " : "", + packet -> raw -> giaddr.s_addr + ? inet_ntoa (packet -> raw -> giaddr) + : packet -> interface -> name); + +#if defined (FAILOVER_PROTOCOL) + if (lease && lease -> pool && lease -> pool -> failover_peer) { + peer = lease -> pool -> failover_peer; + if (peer -> service_state == not_responding || + peer -> service_state == service_startup) { + log_info ("%s: not responding%s", + msgbuf, peer -> nrr); + goto out; + } + + /* "load balance to peer" - is not done at all for request. + * + * If it's RENEWING, we are the only server to hear it, so + * we have to serve it. If it's REBINDING, it's out of + * communication with the other server, so there's no point + * in waiting to serve it. However, if the lease we're + * offering is not a free lease, then we may be the only + * server that can offer it, so we can't load balance if + * the lease isn't in the free or backup state. If it is + * in the free or backup state, then that state is what + * mandates one server or the other should perform the + * allocation, not the LBA...we know the peer cannot + * allocate a request for an address in our free state. + * + * So our only compass is lease_mine_to_reallocate(). This + * effects both load balancing, and a sanity-check that we + * are not going to try to allocate a lease that isn't ours. + */ + if ((lease -> binding_state == FTS_FREE || + lease -> binding_state == FTS_BACKUP) && + !lease_mine_to_reallocate (lease)) { + log_debug ("%s: lease owned by peer", msgbuf); + goto out; + } + + /* + * If the lease is in a transitional state, we can't + * renew it unless we can rewind it to a non-transitional + * state (active, free, or backup). lease_mine_to_reallocate() + * checks for free/backup, so we only need to check for active. + */ + if ((lease->binding_state == FTS_RELEASED || + lease->binding_state == FTS_EXPIRED) && + lease->rewind_binding_state != FTS_ACTIVE && + !lease_mine_to_reallocate(lease)) { + log_debug("%s: lease in transition state %s", msgbuf, + (lease->binding_state == FTS_RELEASED) + ? "released" : "expired"); + goto out; + } + + /* It's actually very unlikely that we'll ever get here, + but if we do, tell the client to stop using the lease, + because the administrator reset it. */ + if (lease -> binding_state == FTS_RESET && + !lease_mine_to_reallocate (lease)) { + log_debug ("%s: lease reset by administrator", msgbuf); + nak_lease (packet, &cip); + goto out; + } + + /* At this point it's possible that we will get a broadcast + DHCPREQUEST for a lease that we didn't offer, because + both we and the peer are in a position to offer it. + In that case, we probably shouldn't answer. In order + to not answer, we would have to compare the server + identifier sent by the client with the list of possible + server identifiers we can send, and if the client's + identifier isn't on the list, drop the DHCPREQUEST. + We aren't currently doing that for two reasons - first, + it's not clear that all clients do the right thing + with respect to sending the client identifier, which + could mean that we might simply not respond to a client + that is depending on us to respond. Secondly, we allow + the user to specify the server identifier to send, and + we don't enforce that the server identifier should be + one of our IP addresses. This is probably not a big + deal, but it's theoretically an issue. + + The reason we care about this is that if both servers + send a DHCPACK to the DHCPREQUEST, they are then going + to send dueling BNDUPD messages, which could cause + trouble. I think it causes no harm, but it seems + wrong. */ + } else + peer = (dhcp_failover_state_t *)0; +#endif + + /* 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) || + (have_requested_addr && !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. */ + if (!packet -> shared_network) { + if (subnet && subnet -> group -> authoritative) { + log_info ("%s: wrong network.", msgbuf); + nak_lease (packet, &cip); + goto out; + } + /* Otherwise, ignore it. */ + log_info ("%s: ignored (%s).", msgbuf, + (subnet + ? "not authoritative" : "unknown subnet")); + goto out; + } + + /* If we do know where it came from and it asked for an + address that is not on that shared network, nak it. */ + if (subnet) + subnet_dereference (&subnet, MDL); + if (!find_grouped_subnet (&subnet, packet -> shared_network, + cip, MDL)) { + if (packet -> shared_network -> group -> authoritative) + { + log_info ("%s: wrong network.", msgbuf); + nak_lease (packet, &cip); + goto out; + } + log_info ("%s: ignored (not authoritative).", msgbuf); + return; + } + } + + /* If the address the client asked for is ours, but it wasn't + available for the client, NAK it. */ + if (!lease && ours) { + log_info ("%s: lease %s unavailable.", msgbuf, piaddr (cip)); + nak_lease (packet, &cip); + goto out; + } + + /* Otherwise, send the lease to the client if we found one. */ + if (lease) { + ack_lease (packet, lease, DHCPACK, 0, msgbuf, ms_nulltp, + (struct host_decl *)0); + } else + log_info ("%s: unknown lease %s.", msgbuf, piaddr (cip)); + + out: + if (subnet) + subnet_dereference (&subnet, MDL); + if (lease) + lease_dereference (&lease, MDL); + return; +} + +void dhcprelease (packet, ms_nulltp) + struct packet *packet; + int ms_nulltp; +{ + struct lease *lease = (struct lease *)0, *next = (struct lease *)0; + struct iaddr cip; + struct option_cache *oc; + struct data_string data; + const char *s; + char msgbuf [1024], cstr[16]; /* XXX */ + + + /* DHCPRELEASE must not specify address in requested-address + option, but old protocol specs weren't explicit about this, + so let it go. */ + if ((oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_REQUESTED_ADDRESS))) { + log_info ("DHCPRELEASE from %s specified requested-address.", + print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr)); + } + + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_CLIENT_IDENTIFIER); + memset (&data, 0, sizeof data); + if (oc && + evaluate_option_cache (&data, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, (struct option_state *)0, + &global_scope, oc, MDL)) { + find_lease_by_uid (&lease, data.data, data.len, MDL); + data_string_forget (&data, MDL); + + /* See if we can find a lease that matches the IP address + the client is claiming. */ + while (lease) { + if (lease -> n_uid) + lease_reference (&next, lease -> n_uid, MDL); + if (!memcmp (&packet -> raw -> ciaddr, + lease -> ip_addr.iabuf, 4)) { + break; + } + lease_dereference (&lease, MDL); + if (next) { + lease_reference (&lease, next, MDL); + lease_dereference (&next, MDL); + } + } + if (next) + lease_dereference (&next, MDL); + } + + /* 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. */ + if (!lease) { + cip.len = 4; + memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4); + find_lease_by_ip_addr (&lease, cip, MDL); + } + + + /* If the hardware address doesn't match, don't do the release. */ + if (lease && + (lease -> hardware_addr.hlen != packet -> raw -> hlen + 1 || + lease -> hardware_addr.hbuf [0] != packet -> raw -> htype || + memcmp (&lease -> hardware_addr.hbuf [1], + packet -> raw -> chaddr, packet -> raw -> hlen))) + lease_dereference (&lease, MDL); + + if (lease && lease -> client_hostname) { + if ((strlen (lease -> client_hostname) <= 64) && + db_printable((unsigned char *)lease->client_hostname)) + s = lease -> client_hostname; + else + s = "Hostname Unsuitable for Printing"; + } else + s = (char *)0; + + /* %Audit% Cannot exceed 16 bytes. %2004.06.17,Safe% + * We copy this out to stack because we actually want to log two + * inet_ntoa()'s in this message. + */ + strncpy(cstr, inet_ntoa (packet -> raw -> ciaddr), 15); + cstr[15] = '\0'; + + /* %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, + "DHCPRELEASE of %s from %s %s%s%svia %s (%sfound)", + cstr, + (packet -> raw -> htype + ? print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr) + : (lease + ? print_hex_1(lease->uid_len, lease->uid, 60) + : "<no identifier>")), + s ? "(" : "", s ? s : "", s ? ") " : "", + packet -> raw -> giaddr.s_addr + ? inet_ntoa (packet -> raw -> giaddr) + : packet -> interface -> name, + lease ? "" : "not "); + +#if defined (FAILOVER_PROTOCOL) + if (lease && lease -> pool && lease -> pool -> failover_peer) { + dhcp_failover_state_t *peer = lease -> pool -> failover_peer; + if (peer -> service_state == not_responding || + peer -> service_state == service_startup) { + log_info ("%s: ignored%s", + peer -> name, peer -> nrr); + goto out; + } + + /* DHCPRELEASE messages are unicast, so if the client + sent the DHCPRELEASE to us, it's not going to send it + to the peer. Not sure why this would happen, and + if it does happen I think we still have to change the + lease state, so that's what we're doing. + XXX See what it says in the draft about this. */ + } +#endif + + /* If we found a lease, release it. */ + if (lease && lease -> ends > cur_time) { + release_lease (lease, packet); + } + log_info ("%s", msgbuf); +#if defined(FAILOVER_PROTOCOL) + out: +#endif + if (lease) + lease_dereference (&lease, MDL); +} + +void dhcpdecline (packet, ms_nulltp) + struct packet *packet; + int ms_nulltp; +{ + struct lease *lease = (struct lease *)0; + struct option_state *options = (struct option_state *)0; + int ignorep = 0; + int i; + const char *status; + const char *s; + char msgbuf [1024]; /* XXX */ + struct iaddr cip; + struct option_cache *oc; + struct data_string data; + + /* DHCPDECLINE must specify address. */ + if (!(oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_REQUESTED_ADDRESS))) + return; + memset (&data, 0, sizeof data); + if (!evaluate_option_cache (&data, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, + (struct option_state *)0, + &global_scope, oc, MDL)) + return; + + cip.len = 4; + memcpy (cip.iabuf, data.data, 4); + data_string_forget (&data, MDL); + find_lease_by_ip_addr (&lease, cip, MDL); + + if (lease && lease -> client_hostname) { + if ((strlen (lease -> client_hostname) <= 64) && + db_printable((unsigned char *)lease->client_hostname)) + s = lease -> client_hostname; + else + s = "Hostname Unsuitable for Printing"; + } else + s = (char *)0; + + /* %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, + "DHCPDECLINE of %s from %s %s%s%svia %s", + piaddr (cip), + (packet -> raw -> htype + ? print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr) + : (lease + ? print_hex_1(lease->uid_len, lease->uid, 60) + : "<no identifier>")), + s ? "(" : "", s ? s : "", s ? ") " : "", + packet -> raw -> giaddr.s_addr + ? inet_ntoa (packet -> raw -> giaddr) + : packet -> interface -> name); + + option_state_allocate (&options, MDL); + + /* Execute statements in scope starting with the subnet scope. */ + if (lease) + execute_statements_in_scope ((struct binding_value **)0, + packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, options, + &global_scope, + lease -> subnet -> group, + (struct group *)0); + + /* Execute statements in the class scopes. */ + for (i = packet -> class_count; i > 0; i--) { + execute_statements_in_scope + ((struct binding_value **)0, packet, (struct lease *)0, + (struct client_state *)0, packet -> options, options, + &global_scope, packet -> classes [i - 1] -> group, + lease ? lease -> subnet -> group : (struct group *)0); + } + + /* Drop the request if dhcpdeclines are being ignored. */ + oc = lookup_option (&server_universe, options, SV_DECLINES); + if (!oc || + evaluate_boolean_option_cache (&ignorep, packet, lease, + (struct client_state *)0, + packet -> options, options, + &lease -> scope, oc, MDL)) { + /* If we found a lease, mark it as unusable and complain. */ + if (lease) { +#if defined (FAILOVER_PROTOCOL) + if (lease -> pool && lease -> pool -> failover_peer) { + dhcp_failover_state_t *peer = + lease -> pool -> failover_peer; + if (peer -> service_state == not_responding || + peer -> service_state == service_startup) { + if (!ignorep) + log_info ("%s: ignored%s", + peer -> name, peer -> nrr); + goto out; + } + + /* DHCPDECLINE messages are broadcast, so we can safely + ignore the DHCPDECLINE if the peer has the lease. + XXX Of course, at this point that information has been + lost. */ + } +#endif + + abandon_lease (lease, "declined."); + status = "abandoned"; + } else { + status = "not found"; + } + } else + status = "ignored"; + + if (!ignorep) + log_info ("%s: %s", msgbuf, status); + +#if defined(FAILOVER_PROTOCOL) + out: +#endif + if (options) + option_state_dereference (&options, MDL); + if (lease) + lease_dereference (&lease, MDL); +} + +void dhcpinform (packet, ms_nulltp) + struct packet *packet; + int ms_nulltp; +{ + char msgbuf [1024]; + struct data_string d1, prl; + struct option_cache *oc; + struct option_state *options = (struct option_state *)0; + struct dhcp_packet raw; + struct packet outgoing; + unsigned char dhcpack = DHCPACK; + struct subnet *subnet = NULL; + struct iaddr cip, gip; + unsigned i; + int nulltp; + struct sockaddr_in to; + struct in_addr from; + isc_boolean_t zeroed_ciaddr; + + /* The client should set ciaddr to its IP address, but apparently + it's common for clients not to do this, so we'll use their IP + source address if they didn't set ciaddr. */ + if (!packet -> raw -> ciaddr.s_addr) { + zeroed_ciaddr = ISC_TRUE; + cip.len = 4; + memcpy (cip.iabuf, &packet -> client_addr.iabuf, 4); + } else { + zeroed_ciaddr = ISC_FALSE; + cip.len = 4; + memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4); + } + + if (packet->raw->giaddr.s_addr) { + gip.len = 4; + memcpy(gip.iabuf, &packet->raw->giaddr, 4); + } else + gip.len = 0; + + /* %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, "DHCPINFORM from %s via %s", + piaddr (cip), packet->raw->giaddr.s_addr ? + inet_ntoa(packet->raw->giaddr) : + packet -> interface -> name); + + /* If the IP source address is zero, don't respond. */ + if (!memcmp (cip.iabuf, "\0\0\0", 4)) { + log_info ("%s: ignored (null source address).", msgbuf); + return; + } + + /* Find the subnet that the client is on. */ + if (zeroed_ciaddr && (gip.len != 0)) { + /* XXX - do subnet selection relay agent suboption here */ + find_subnet(&subnet, gip, MDL); + + if (subnet == NULL) { + log_info("%s: unknown subnet for relay address %s", + msgbuf, piaddr(gip)); + return; + } + } else { + /* XXX - do subnet selection (not relay agent) option here */ + find_subnet(&subnet, cip, MDL); + + if (subnet == NULL) { + log_info("%s: unknown subnet for %s address %s", + msgbuf, zeroed_ciaddr ? "source" : "client", + piaddr(cip)); + return; + } + } + + /* We don't respond to DHCPINFORM packets if we're not authoritative. + It would be nice if a per-host value could override this, but + there's overhead involved in checking this, so let's see how people + react first. */ + if (subnet && !subnet -> group -> authoritative) { + static int eso = 0; + log_info ("%s: not authoritative for subnet %s", + msgbuf, piaddr (subnet -> net)); + if (!eso) { + log_info ("If this DHCP server is authoritative for%s", + " that subnet,"); + log_info ("please write an `authoritative;' directi%s", + "ve either in the"); + log_info ("subnet declaration or in some scope that%s", + " encloses the"); + log_info ("subnet declaration - for example, write %s", + "it at the top"); + log_info ("of the dhcpd.conf file."); + } + if (eso++ == 100) + eso = 0; + subnet_dereference (&subnet, MDL); + return; + } + + option_state_allocate (&options, MDL); + memset (&outgoing, 0, sizeof outgoing); + memset (&raw, 0, sizeof raw); + outgoing.raw = &raw; + + maybe_return_agent_options(packet, options); + + /* Execute statements in scope starting with the subnet scope. */ + if (subnet) + execute_statements_in_scope ((struct binding_value **)0, + packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, options, + &global_scope, subnet -> group, + (struct group *)0); + + /* Execute statements in the class scopes. */ + for (i = packet -> class_count; i > 0; i--) { + execute_statements_in_scope + ((struct binding_value **)0, packet, (struct lease *)0, + (struct client_state *)0, packet -> options, options, + &global_scope, packet -> classes [i - 1] -> group, + subnet ? subnet -> group : (struct group *)0); + } + + /* Figure out the filename. */ + memset (&d1, 0, sizeof d1); + oc = lookup_option (&server_universe, options, SV_FILENAME); + if (oc && + evaluate_option_cache (&d1, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, (struct option_state *)0, + &global_scope, oc, MDL)) { + i = d1.len; + if (i >= sizeof(raw.file)) { + log_info("file name longer than packet field " + "truncated - field: %lu name: %d %.*s", + (unsigned long)sizeof(raw.file), i, i, + d1.data); + i = sizeof(raw.file); + } else + raw.file[i] = 0; + memcpy (raw.file, d1.data, i); + data_string_forget (&d1, MDL); + } + + /* Choose a server name as above. */ + oc = lookup_option (&server_universe, options, SV_SERVER_NAME); + if (oc && + evaluate_option_cache (&d1, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, (struct option_state *)0, + &global_scope, oc, MDL)) { + i = d1.len; + if (i >= sizeof(raw.sname)) { + log_info("server name longer than packet field " + "truncated - field: %lu name: %d %.*s", + (unsigned long)sizeof(raw.sname), i, i, + d1.data); + i = sizeof(raw.sname); + } else + raw.sname[i] = 0; + memcpy (raw.sname, d1.data, i); + data_string_forget (&d1, MDL); + } + + /* Set a flag if this client is a lame Microsoft client that NUL + terminates string options and expects us to do likewise. */ + nulltp = 0; + if ((oc = lookup_option (&dhcp_universe, packet -> options, + DHO_HOST_NAME))) { + if (!oc->expression) + nulltp = oc->flags & OPTION_HAD_NULLS; + } + + /* Put in DHCP-specific options. */ + i = DHO_DHCP_MESSAGE_TYPE; + oc = (struct option_cache *)0; + if (option_cache_allocate (&oc, MDL)) { + if (make_const_data (&oc -> expression, + &dhcpack, 1, 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); + } + + get_server_source_address(&from, options, packet); + + /* Use the subnet mask from the subnet declaration if no other + mask has been provided. */ + i = DHO_SUBNET_MASK; + if (subnet && !lookup_option (&dhcp_universe, options, i)) { + oc = (struct option_cache *)0; + if (option_cache_allocate (&oc, MDL)) { + if (make_const_data (&oc -> expression, + subnet -> netmask.iabuf, + 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); + } + } + + /* If a site option space has been specified, use that for + site option codes. */ + i = SV_SITE_OPTION_SPACE; + if ((oc = lookup_option (&server_universe, options, i)) && + evaluate_option_cache (&d1, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, options, + &global_scope, oc, MDL)) { + struct universe *u = (struct universe *)0; + + if (!universe_hash_lookup (&u, universe_hash, + (const char *)d1.data, d1.len, + MDL)) { + log_error ("unknown option space %s.", d1.data); + option_state_dereference (&options, MDL); + if (subnet) + subnet_dereference (&subnet, MDL); + return; + } + + options -> site_universe = u -> index; + options->site_code_min = find_min_site_code(u); + data_string_forget (&d1, MDL); + } else { + options -> site_universe = dhcp_universe.index; + options -> site_code_min = 0; /* Trust me, it works. */ + } + + memset (&prl, 0, sizeof prl); + + /* Use the parameter list from the scope if there is one. */ + oc = lookup_option (&dhcp_universe, options, + DHO_DHCP_PARAMETER_REQUEST_LIST); + + /* Otherwise, if the client has provided a list of options + that it wishes returned, use it to prioritize. Otherwise, + prioritize based on the default priority list. */ + + if (!oc) + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_PARAMETER_REQUEST_LIST); + + if (oc) + evaluate_option_cache (&prl, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, options, + &global_scope, oc, MDL); + +#ifdef DEBUG_PACKET + dump_packet (packet); + dump_raw ((unsigned char *)packet -> raw, packet -> packet_length); +#endif + + log_info ("%s", msgbuf); + + /* Figure out the address of the boot file server. */ + if ((oc = + lookup_option (&server_universe, options, SV_NEXT_SERVER))) { + if (evaluate_option_cache (&d1, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, options, + &global_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); + } + } + + /* + * Remove any time options, per section 3.4 RFC 2131 + */ + delete_option(&dhcp_universe, options, DHO_DHCP_LEASE_TIME); + delete_option(&dhcp_universe, options, DHO_DHCP_RENEWAL_TIME); + delete_option(&dhcp_universe, options, DHO_DHCP_REBINDING_TIME); + + /* Set up the option buffer... */ + outgoing.packet_length = + cons_options (packet, outgoing.raw, (struct lease *)0, + (struct client_state *)0, + 0, packet -> options, options, &global_scope, + 0, nulltp, 0, + prl.len ? &prl : (struct data_string *)0, + (char *)0); + option_state_dereference (&options, MDL); + data_string_forget (&prl, MDL); + + /* 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; + + raw.giaddr = packet -> raw -> giaddr; + raw.ciaddr = packet -> raw -> ciaddr; + 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; + raw.hops = packet -> raw -> hops; + raw.op = BOOTREPLY; + +#ifdef DEBUG_PACKET + dump_packet (&outgoing); + dump_raw ((unsigned char *)&raw, outgoing.packet_length); +#endif + + /* Set up the common stuff... */ + to.sin_family = AF_INET; +#ifdef HAVE_SA_LEN + to.sin_len = sizeof to; +#endif + memset (to.sin_zero, 0, sizeof to.sin_zero); + + /* RFC2131 states the server SHOULD unciast to ciaddr. + * There are two wrinkles - relays, and when ciaddr is zero. + * There's actually no mention of relays at all in rfc2131 in + * regard to DHCPINFORM, except to say we might get packets from + * clients via them. Note: relays unicast to clients to the + * "yiaddr" address, which servers are forbidden to set when + * answering an inform. + * + * The solution: If ciaddr is zero, and giaddr is set, go via the + * relay with the broadcast flag set to help the relay (with no + * yiaddr and very likely no chaddr, it will have no idea where to + * send the packet). + * + * If the ciaddr is zero and giaddr is not set, go via the source + * IP address (but you are permitted to barf on their shoes). + * + * If ciaddr is not zero, send the packet there always. + */ + if (!raw.ciaddr.s_addr && gip.len) { + memcpy(&to.sin_addr, gip.iabuf, 4); + to.sin_port = local_port; + raw.flags |= htons(BOOTP_BROADCAST); + } else { + gip.len = 0; + memcpy(&to.sin_addr, cip.iabuf, 4); + to.sin_port = remote_port; + } + + /* Report what we're sending. */ + snprintf(msgbuf, sizeof msgbuf, "DHCPACK to %s (%s) via", piaddr(cip), + (packet->raw->htype && packet->raw->hlen) ? + print_hw_addr(packet->raw->htype, packet->raw->hlen, + packet->raw->chaddr) : + "<no client hardware address>"); + log_info("%s %s", msgbuf, gip.len ? piaddr(gip) : + packet->interface->name); + + errno = 0; + send_packet ((fallback_interface + ? fallback_interface : packet -> interface), + &outgoing, &raw, outgoing.packet_length, + from, &to, (struct hardware *)0); + if (subnet) + subnet_dereference (&subnet, MDL); +} + +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; + unsigned i; + struct option_state *options = (struct option_state *)0; + struct option_cache *oc = (struct option_cache *)0; + + option_state_allocate (&options, MDL); + memset (&outgoing, 0, sizeof outgoing); + memset (&raw, 0, sizeof raw); + outgoing.raw = &raw; + + /* Set DHCP_MESSAGE_TYPE to DHCPNAK */ + if (!option_cache_allocate (&oc, MDL)) { + log_error ("No memory for DHCPNAK message type."); + option_state_dereference (&options, MDL); + return; + } + if (!make_const_data (&oc -> expression, &nak, sizeof nak, + 0, 0, MDL)) { + log_error ("No memory for expr_const expression."); + option_cache_dereference (&oc, MDL); + option_state_dereference (&options, MDL); + return; + } + i = DHO_DHCP_MESSAGE_TYPE; + option_code_hash_lookup(&oc->option, dhcp_universe.code_hash, + &i, 0, MDL); + save_option (&dhcp_universe, options, oc); + option_cache_dereference (&oc, MDL); + + /* Set DHCP_MESSAGE to whatever the message is */ + if (!option_cache_allocate (&oc, MDL)) { + log_error ("No memory for DHCPNAK message type."); + option_state_dereference (&options, MDL); + return; + } + if (!make_const_data (&oc -> expression, + (unsigned char *)dhcp_message, + strlen (dhcp_message), 1, 0, MDL)) { + log_error ("No memory for expr_const expression."); + option_cache_dereference (&oc, MDL); + option_state_dereference (&options, MDL); + return; + } + i = DHO_DHCP_MESSAGE; + option_code_hash_lookup(&oc->option, dhcp_universe.code_hash, + &i, 0, MDL); + save_option (&dhcp_universe, options, oc); + option_cache_dereference (&oc, MDL); + + get_server_source_address(&from, options, packet); + + /* If there were agent options in the incoming packet, return + * them. We do not check giaddr to detect the presence of a + * relay, as this excludes "l2" relay agents which have no + * giaddr to set. + */ + if (packet->options->universe_count > agent_universe.index && + packet->options->universes [agent_universe.index]) { + option_chain_head_reference + ((struct option_chain_head **) + &(options -> universes [agent_universe.index]), + (struct option_chain_head *) + packet -> options -> universes [agent_universe.index], + MDL); + } + + /* Do not use the client's requested parameter list. */ + delete_option (&dhcp_universe, packet -> options, + DHO_DHCP_PARAMETER_REQUEST_LIST); + + /* Set up the option buffer... */ + outgoing.packet_length = + cons_options (packet, outgoing.raw, (struct lease *)0, + (struct client_state *)0, + 0, packet -> options, options, &global_scope, + 0, 0, 0, (struct data_string *)0, (char *)0); + option_state_dereference (&options, MDL); + +/* memset (&raw.ciaddr, 0, sizeof raw.ciaddr);*/ + if (packet->interface->address_count) + raw.siaddr = packet->interface->addresses[0]; + 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... */ + log_info ("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 + + /* Set up the common stuff... */ + to.sin_family = AF_INET; +#ifdef HAVE_SA_LEN + to.sin_len = sizeof to; +#endif + memset (to.sin_zero, 0, sizeof to.sin_zero); + + /* 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; + if (raw.giaddr.s_addr != htonl (INADDR_LOOPBACK)) + to.sin_port = local_port; + else + to.sin_port = remote_port; /* for testing. */ + + if (fallback_interface) { + result = send_packet(fallback_interface, packet, &raw, + outgoing.packet_length, from, &to, + NULL); + return; + } + } else { + to.sin_addr = limited_broadcast; + to.sin_port = remote_port; + } + + errno = 0; + result = send_packet(packet->interface, packet, &raw, + outgoing.packet_length, from, &to, NULL); +} + +void ack_lease (packet, lease, offer, when, msg, ms_nulltp, hp) + struct packet *packet; + struct lease *lease; + unsigned int offer; + TIME when; + char *msg; + int ms_nulltp; + struct host_decl *hp; +{ + struct lease *lt; + struct lease_state *state; + struct lease *next; + struct host_decl *host = (struct host_decl *)0; + TIME lease_time; + TIME offered_lease_time; + struct data_string d1; + TIME min_lease_time; + TIME max_lease_time; + TIME default_lease_time; + struct option_cache *oc; + isc_result_t result; + TIME ping_timeout; + TIME lease_cltt; + struct in_addr from; + TIME remaining_time; + struct iaddr cip; +#if defined(DELAYED_ACK) + isc_boolean_t enqueue = ISC_TRUE; +#endif + + unsigned i, j; + int s1; + int ignorep; + struct timeval tv; + + /* If we're already acking this lease, don't do it again. */ + if (lease -> state) + return; + + /* Save original cltt for comparison later. */ + lease_cltt = lease->cltt; + + /* If the lease carries a host record, remember it. */ + if (hp) + host_reference (&host, hp, MDL); + else if (lease -> host) + host_reference (&host, lease -> host, MDL); + + /* Allocate a lease state structure... */ + state = new_lease_state (MDL); + if (!state) + log_fatal ("unable to allocate lease state!"); + state -> got_requested_address = packet -> got_requested_address; + shared_network_reference (&state -> shared_network, + packet -> interface -> shared_network, MDL); + + /* See if we got a server identifier option. */ + if (lookup_option (&dhcp_universe, + packet -> options, DHO_DHCP_SERVER_IDENTIFIER)) + state -> got_server_identifier = 1; + + maybe_return_agent_options(packet, state->options); + + /* If we are offering a lease that is still currently valid, preserve + the events. We need to do this because if the client does not + REQUEST our offer, it will expire in 2 minutes, overriding the + expire time in the currently in force lease. We want the expire + events to be executed at that point. */ + if (lease -> ends <= cur_time && offer != DHCPOFFER) { + /* Get rid of any old expiry or release statements - by + executing the statements below, we will be inserting new + ones if there are any to insert. */ + if (lease -> on_expiry) + executable_statement_dereference (&lease -> on_expiry, + MDL); + if (lease -> on_commit) + executable_statement_dereference (&lease -> on_commit, + MDL); + if (lease -> on_release) + executable_statement_dereference (&lease -> on_release, + MDL); + } + + /* Execute statements in scope starting with the subnet scope. */ + execute_statements_in_scope ((struct binding_value **)0, + packet, lease, (struct client_state *)0, + packet -> options, + state -> options, &lease -> scope, + lease -> subnet -> group, + (struct group *)0); + + /* If the lease is from a pool, run the pool scope. */ + if (lease -> pool) + (execute_statements_in_scope + ((struct binding_value **)0, packet, lease, + (struct client_state *)0, packet -> options, + state -> options, &lease -> scope, lease -> pool -> group, + lease -> pool -> shared_network -> group)); + + /* 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, state -> options, + &lease -> scope, packet -> classes [i - 1] -> group, + (lease -> pool + ? lease -> pool -> group + : lease -> subnet -> group)); + } + + /* See if the client is only supposed to have one lease at a time, + and if so, find its other leases and release them. We can only + do this on DHCPREQUEST. It's a little weird to do this before + looking at permissions, because the client might not actually + _get_ a lease after we've done the permission check, but the + assumption for this option is that the client has exactly one + network interface, and will only ever remember one lease. So + if it sends a DHCPREQUEST, and doesn't get the lease, it's already + forgotten about its old lease, so we can too. */ + if (packet -> packet_type == DHCPREQUEST && + (oc = lookup_option (&server_universe, state -> options, + SV_ONE_LEASE_PER_CLIENT)) && + evaluate_boolean_option_cache (&ignorep, + packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, &lease -> scope, + oc, MDL)) { + struct lease *seek; + if (lease -> uid_len) { + do { + seek = (struct lease *)0; + find_lease_by_uid (&seek, lease -> uid, + lease -> uid_len, MDL); + if (!seek) + break; + if (seek == lease && !seek -> n_uid) { + lease_dereference (&seek, MDL); + break; + } + next = (struct lease *)0; + + /* Don't release expired leases, and don't + release the lease we're going to assign. */ + next = (struct lease *)0; + while (seek) { + if (seek -> n_uid) + lease_reference (&next, seek -> n_uid, MDL); + if (seek != lease && + seek -> binding_state != FTS_RELEASED && + seek -> binding_state != FTS_EXPIRED && + seek -> binding_state != FTS_RESET && + seek -> binding_state != FTS_FREE && + seek -> binding_state != FTS_BACKUP) + break; + lease_dereference (&seek, MDL); + if (next) { + lease_reference (&seek, next, MDL); + lease_dereference (&next, MDL); + } + } + if (next) + lease_dereference (&next, MDL); + if (seek) { + release_lease (seek, packet); + lease_dereference (&seek, MDL); + } else + break; + } while (1); + } + if (!lease -> uid_len || + (host && + !host -> client_identifier.len && + (oc = lookup_option (&server_universe, state -> options, + SV_DUPLICATES)) && + !evaluate_boolean_option_cache (&ignorep, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, + oc, MDL))) { + do { + seek = (struct lease *)0; + find_lease_by_hw_addr + (&seek, lease -> hardware_addr.hbuf, + lease -> hardware_addr.hlen, MDL); + if (!seek) + break; + if (seek == lease && !seek -> n_hw) { + lease_dereference (&seek, MDL); + break; + } + next = (struct lease *)0; + while (seek) { + if (seek -> n_hw) + lease_reference (&next, seek -> n_hw, MDL); + if (seek != lease && + seek -> binding_state != FTS_RELEASED && + seek -> binding_state != FTS_EXPIRED && + seek -> binding_state != FTS_RESET && + seek -> binding_state != FTS_FREE && + seek -> binding_state != FTS_BACKUP) + break; + lease_dereference (&seek, MDL); + if (next) { + lease_reference (&seek, next, MDL); + lease_dereference (&next, MDL); + } + } + if (next) + lease_dereference (&next, MDL); + if (seek) { + release_lease (seek, packet); + lease_dereference (&seek, MDL); + } else + break; + } while (1); + } + } + + + /* Make sure this packet satisfies the configured minimum + number of seconds. */ + memset (&d1, 0, sizeof d1); + if (offer == DHCPOFFER && + (oc = lookup_option (&server_universe, state -> options, + SV_MIN_SECS))) { + if (evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len && + ntohs (packet -> raw -> secs) < d1.data [0]) { + log_info("%s: configured min-secs value (%d) " + "is greater than secs field (%d). " + "message dropped.", msg, d1.data[0], + ntohs(packet->raw->secs)); + data_string_forget (&d1, MDL); + free_lease_state (state, MDL); + if (host) + host_dereference (&host, MDL); + return; + } + data_string_forget (&d1, MDL); + } + } + + /* Try to find a matching host declaration for this lease. + */ + if (!host) { + struct host_decl *hp = (struct host_decl *)0; + struct host_decl *h; + + /* 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. */ + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_CLIENT_IDENTIFIER); + if (oc && + evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL)) { + find_hosts_by_uid (&hp, d1.data, d1.len, MDL); + data_string_forget (&d1, MDL); + for (h = hp; h; h = h -> n_ipaddr) { + if (!h -> fixed_addr) + break; + } + if (h) + host_reference (&host, h, MDL); + if (hp != NULL) + host_dereference(&hp, MDL); + } + if (!host) { + 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) + break; + } + if (h) + host_reference (&host, h, MDL); + if (hp != NULL) + host_dereference(&hp, MDL); + } + if (!host) { + find_hosts_by_option(&hp, packet, packet->options, MDL); + for (h = hp; h; h = h -> n_ipaddr) { + if (!h -> fixed_addr) + break; + } + if (h) + host_reference (&host, h, MDL); + if (hp != NULL) + host_dereference(&hp, MDL); + } + } + + /* If we have a host_decl structure, run the options associated + with its group. Whether the host decl struct is old or not. */ + if (host) + execute_statements_in_scope ((struct binding_value **)0, + packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, &lease -> scope, + host -> group, + (lease -> pool + ? lease -> pool -> group + : lease -> subnet -> group)); + + /* Drop the request if it's not allowed for this client. By + default, unknown clients are allowed. */ + if (!host && + (oc = lookup_option (&server_universe, state -> options, + SV_BOOT_UNKNOWN_CLIENTS)) && + !evaluate_boolean_option_cache (&ignorep, + packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (!ignorep) + log_info ("%s: unknown client", msg); + free_lease_state (state, MDL); + if (host) + host_dereference (&host, MDL); + return; + } + + /* Drop the request if it's not allowed for this client. */ + if (!offer && + (oc = lookup_option (&server_universe, state -> options, + SV_ALLOW_BOOTP)) && + !evaluate_boolean_option_cache (&ignorep, + packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (!ignorep) + log_info ("%s: bootp disallowed", msg); + free_lease_state (state, MDL); + if (host) + host_dereference (&host, MDL); + return; + } + + /* Drop the request if booting is specifically denied. */ + oc = lookup_option (&server_universe, state -> options, + SV_ALLOW_BOOTING); + if (oc && + !evaluate_boolean_option_cache (&ignorep, + packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (!ignorep) + log_info ("%s: booting disallowed", msg); + free_lease_state (state, MDL); + if (host) + host_dereference (&host, MDL); + return; + } + + /* If we are configured to do per-class billing, do it. */ + if (have_billing_classes && !(lease -> flags & STATIC_LEASE)) { + /* See if the lease is currently being billed to a + class, and if so, whether or not it can continue to + be billed to that class. */ + if (lease -> billing_class) { + for (i = 0; i < packet -> class_count; i++) + if (packet -> classes [i] == + lease -> billing_class) + break; + if (i == packet -> class_count) + unbill_class (lease, lease -> billing_class); + } + + /* If we don't have an active billing, see if we need + one, and if we do, try to do so. */ + if (lease->billing_class == NULL) { + int bill = 0; + for (i = 0; i < packet->class_count; i++) { + if (packet->classes[i]->lease_limit) { + bill++; + if (bill_class(lease, + packet->classes[i])) + break; + } + } + if (bill != 0 && i == packet->class_count) { + log_info("%s: no available billing: lease " + "limit reached in all matching " + "classes", msg); + free_lease_state(state, MDL); + if (host) + host_dereference(&host, MDL); + return; + } + + /* If this is an offer, undo the billing. We go + * through all the steps above to bill a class so + * we can hit the 'no available billing' mark and + * abort without offering. But it just doesn't make + * sense to permanently bill a class for a non-active + * lease. This means on REQUEST, we will bill this + * lease again (if there is a REQUEST). + */ + if (offer == DHCPOFFER && + lease->billing_class != NULL && + lease->binding_state != FTS_ACTIVE) + unbill_class(lease, lease->billing_class); + } + } + + /* Figure out the filename. */ + oc = lookup_option (&server_universe, state -> options, SV_FILENAME); + if (oc) + evaluate_option_cache (&state -> filename, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL); + + /* Choose a server name as above. */ + oc = lookup_option (&server_universe, state -> options, + SV_SERVER_NAME); + if (oc) + evaluate_option_cache (&state -> server_name, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL); + + /* 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. */ + lt = (struct lease *)0; + result = lease_allocate (<, MDL); + if (result != ISC_R_SUCCESS) { + log_info ("%s: can't allocate temporary lease structure: %s", + msg, isc_result_totext (result)); + free_lease_state (state, MDL); + if (host) + host_dereference (&host, MDL); + return; + } + + /* 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 how long a lease to assign. If this is a + dynamic BOOTP lease, its duration must be infinite. */ + if (offer) { + lt->flags &= ~BOOTP_LEASE; + + default_lease_time = DEFAULT_DEFAULT_LEASE_TIME; + if ((oc = lookup_option (&server_universe, state -> options, + SV_DEFAULT_LEASE_TIME))) { + if (evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len == sizeof (u_int32_t)) + default_lease_time = + getULong (d1.data); + data_string_forget (&d1, MDL); + } + } + + if ((oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_LEASE_TIME))) + s1 = evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL); + else + s1 = 0; + + if (s1 && (d1.len == 4)) { + u_int32_t ones = 0xffffffff; + + /* One potential use of reserved leases is to allow + * clients to signal reservation of their lease. They + * can kinda sorta do this, if you squint hard enough, + * by supplying an 'infinite' requested-lease-time + * option. This is generally bad practice...you want + * clients to return to the server on at least some + * period (days, months, years) to get up-to-date + * config state. So; + * + * 1) A client requests 0xffffffff lease-time. + * 2) The server reserves the lease, and assigns a + * <= max_lease_time lease-time to the client, which + * we presume is much smaller than 0xffffffff. + * 3) The client ultimately fails to renew its lease + * (all clients go offline at some point). + * 4) The server retains the reservation, although + * the lease expires and passes through those states + * as normal, it's placed in the 'reserved' queue, + * and is under no circumstances allocated to any + * clients. + * + * Whether the client knows its reserving its lease or + * not, this can be a handy tool for a sysadmin. + */ + if ((memcmp(d1.data, &ones, 4) == 0) && + (oc = lookup_option(&server_universe, + state->options, + SV_RESERVE_INFINITE)) && + evaluate_boolean_option_cache(&ignorep, packet, + lease, NULL, packet->options, + state->options, &lease->scope, + oc, MDL)) { + lt->flags |= RESERVED_LEASE; + if (!ignorep) + log_info("Infinite-leasetime " + "reservation made on %s.", + piaddr(lt->ip_addr)); + } + + lease_time = getULong (d1.data); + } else + lease_time = default_lease_time; + + if (s1) + data_string_forget(&d1, MDL); + + /* See if there's a maximum lease time. */ + max_lease_time = DEFAULT_MAX_LEASE_TIME; + if ((oc = lookup_option (&server_universe, state -> options, + SV_MAX_LEASE_TIME))) { + if (evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len == sizeof (u_int32_t)) + max_lease_time = + getULong (d1.data); + data_string_forget (&d1, MDL); + } + } + + /* Enforce the maximum lease length. */ + if (lease_time < 0 /* XXX */ + || lease_time > max_lease_time) + lease_time = max_lease_time; + + min_lease_time = DEFAULT_MIN_LEASE_TIME; + if (min_lease_time > max_lease_time) + min_lease_time = max_lease_time; + + if ((oc = lookup_option (&server_universe, state -> options, + SV_MIN_LEASE_TIME))) { + if (evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len == sizeof (u_int32_t)) + min_lease_time = getULong (d1.data); + data_string_forget (&d1, MDL); + } + } + + /* CC: If there are less than + adaptive-lease-time-threshold % free leases, + hand out only short term leases */ + + memset(&d1, 0, sizeof(d1)); + if (lease->pool && + (oc = lookup_option(&server_universe, state->options, + SV_ADAPTIVE_LEASE_TIME_THRESHOLD)) && + evaluate_option_cache(&d1, packet, lease, NULL, + packet->options, state->options, + &lease->scope, oc, MDL)) { + if (d1.len == 1 && d1.data[0] > 0 && + d1.data[0] < 100) { + TIME adaptive_time; + int poolfilled, total, count; + + if (min_lease_time) + adaptive_time = min_lease_time; + else + adaptive_time = DEFAULT_MIN_LEASE_TIME; + + /* Allow the client to keep its lease. */ + if (lease->ends - cur_time > adaptive_time) + adaptive_time = lease->ends - cur_time; + + count = lease->pool->lease_count; + total = count - (lease->pool->free_leases + + lease->pool->backup_leases); + + poolfilled = (total > (INT_MAX / 100)) ? + total / (count / 100) : + (total * 100) / count; + + log_debug("Adap-lease: Total: %d, Free: %d, " + "Ends: %d, Adaptive: %d, Fill: %d, " + "Threshold: %d", + lease->pool->lease_count, + lease->pool->free_leases, + (int)(lease->ends - cur_time), + (int)adaptive_time, poolfilled, + d1.data[0]); + + if (poolfilled >= d1.data[0] && + lease_time > adaptive_time) { + log_info("Pool over threshold, time " + "for %s reduced from %d to " + "%d.", piaddr(lease->ip_addr), + (int)lease_time, + (int)adaptive_time); + + lease_time = adaptive_time; + } + } + data_string_forget(&d1, MDL); + } + + /* a client requests an address which is not yet active*/ + if (lease->pool && lease->pool->valid_from && + cur_time < lease->pool->valid_from) { + /* NAK leases before pool activation date */ + cip.len = 4; + memcpy (cip.iabuf, <->ip_addr.iabuf, 4); + nak_lease(packet, &cip); + free_lease_state (state, MDL); + lease_dereference (<, MDL); + if (host) + host_dereference (&host, MDL); + return; + + } + + /* CC: + a) NAK current lease if past the expiration date + b) extend lease only up to the expiration date, but not + below min-lease-time + Setting min-lease-time is essential for this to work! + The value of min-lease-time determines the lenght + of the transition window: + A client renewing a second before the deadline will + get a min-lease-time lease. Since the current ip might not + be routable after the deadline, the client will + be offline until it DISCOVERS again. Otherwise it will + receive a NAK at T/2. + A min-lease-time of 6 seconds effectively switches over + all clients in this pool very quickly. + */ + + if (lease->pool && lease->pool->valid_until) { + if (cur_time >= lease->pool->valid_until) { + /* NAK leases after pool expiration date */ + cip.len = 4; + memcpy (cip.iabuf, <->ip_addr.iabuf, 4); + nak_lease(packet, &cip); + free_lease_state (state, MDL); + lease_dereference (<, MDL); + if (host) + host_dereference (&host, MDL); + return; + } + remaining_time = lease->pool->valid_until - cur_time; + if (lease_time > remaining_time) + lease_time = remaining_time; + } + + if (lease_time < min_lease_time) { + if (min_lease_time) + lease_time = min_lease_time; + else + lease_time = default_lease_time; + } + + +#if defined (FAILOVER_PROTOCOL) + /* Okay, we know the lease duration. Now check the + failover state, if any. */ + if (lease -> pool && lease -> pool -> failover_peer) { + TIME new_lease_time = lease_time; + dhcp_failover_state_t *peer = + lease -> pool -> failover_peer; + + /* Copy previous lease failover ack-state. */ + lt->tsfp = lease->tsfp; + lt->atsfp = lease->atsfp; + + /* cltt set below */ + + /* Lease times less than MCLT are not a concern. */ + if (lease_time > peer->mclt) { + /* Each server can only offer a lease time + * that is either equal to MCLT (at least), + * or up to TSFP+MCLT. Only if the desired + * lease time falls within TSFP+MCLT, can + * the server allow it. + */ + if (lt->tsfp <= cur_time) + new_lease_time = peer->mclt; + else if ((cur_time + lease_time) > + (lt->tsfp + peer->mclt)) + new_lease_time = (lt->tsfp - cur_time) + + peer->mclt; + } + + /* Update potential expiry. Allow for the desired + * lease time plus one half the actual (whether + * modified downward or not) lease time, which is + * actually an estimate of when the client will + * renew. This way, the client will be able to get + * the desired lease time upon renewal. + */ + if (offer == DHCPACK) { + lt->tstp = cur_time + lease_time + + (new_lease_time / 2); + + /* If we reduced the potential expiry time, + * make sure we don't offer an old-expiry-time + * lease for this lease before the change is + * ack'd. + */ + if (lt->tstp < lt->tsfp) + lt->tsfp = lt->tstp; + } else + lt->tstp = lease->tstp; + + /* Use failover-modified lease time. */ + lease_time = new_lease_time; + } +#endif /* FAILOVER_PROTOCOL */ + + /* If the lease duration causes the time value to wrap, + use the maximum expiry time. */ + if (cur_time + lease_time < cur_time) + state -> offered_expiry = MAX_TIME - 1; + else + state -> offered_expiry = cur_time + lease_time; + if (when) + lt -> ends = when; + else + lt -> ends = state -> offered_expiry; + + /* Don't make lease active until we actually get a + DHCPREQUEST. */ + if (offer == DHCPACK) + lt -> next_binding_state = FTS_ACTIVE; + else + lt -> next_binding_state = lease -> binding_state; + } else { + lt->flags |= BOOTP_LEASE; + + lease_time = MAX_TIME - cur_time; + + if ((oc = lookup_option (&server_universe, state -> options, + SV_BOOTP_LEASE_LENGTH))) { + if (evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len == sizeof (u_int32_t)) + lease_time = getULong (d1.data); + data_string_forget (&d1, MDL); + } + } + + if ((oc = lookup_option (&server_universe, state -> options, + SV_BOOTP_LEASE_CUTOFF))) { + if (evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len == sizeof (u_int32_t)) + lease_time = (getULong (d1.data) - + cur_time); + data_string_forget (&d1, MDL); + } + } + + lt -> ends = state -> offered_expiry = cur_time + lease_time; + lt -> next_binding_state = FTS_ACTIVE; + } + + /* Update Client Last Transaction Time. */ + lt->cltt = cur_time; + + /* Record the uid, if given... */ + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_CLIENT_IDENTIFIER); + if (oc && + evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len <= sizeof lt -> uid_buf) { + memcpy (lt -> uid_buf, d1.data, d1.len); + lt -> uid = lt -> uid_buf; + lt -> uid_max = sizeof lt -> uid_buf; + lt -> uid_len = d1.len; + } else { + unsigned char *tuid; + lt -> uid_max = d1.len; + lt -> uid_len = d1.len; + tuid = (unsigned char *)dmalloc (lt -> uid_max, MDL); + /* XXX inelegant */ + if (!tuid) + log_fatal ("no memory for large uid."); + memcpy (tuid, d1.data, lt -> uid_len); + lt -> uid = tuid; + } + data_string_forget (&d1, MDL); + } + + if (host) { + host_reference (< -> host, host, MDL); + host_dereference (&host, MDL); + } + if (lease -> subnet) + subnet_reference (< -> subnet, lease -> subnet, MDL); + if (lease -> billing_class) + class_reference (< -> billing_class, + lease -> billing_class, MDL); + + /* Set a flag if this client is a broken client that NUL + terminates string options and expects us to do likewise. */ + if (ms_nulltp) + lease -> flags |= MS_NULL_TERMINATION; + else + lease -> flags &= ~MS_NULL_TERMINATION; + + /* Save any bindings. */ + if (lease -> scope) { + binding_scope_reference (< -> scope, lease -> scope, MDL); + binding_scope_dereference (&lease -> scope, MDL); + } + if (lease -> agent_options) + option_chain_head_reference (< -> agent_options, + lease -> agent_options, MDL); + + /* Save the vendor-class-identifier for DHCPLEASEQUERY. */ + oc = lookup_option(&dhcp_universe, packet->options, + DHO_VENDOR_CLASS_IDENTIFIER); + if (oc != NULL && + evaluate_option_cache(&d1, packet, NULL, NULL, packet->options, + NULL, &lease->scope, oc, MDL)) { + if (d1.len != 0) { + bind_ds_value(&lease->scope, "vendor-class-identifier", + &d1); + } + + data_string_forget(&d1, MDL); + } + + /* If we got relay agent information options from the packet, then + * cache them for renewal in case the relay agent can't supply them + * when the client unicasts. The options may be from an addressed + * "l3" relay, or from an unaddressed "l2" relay which does not set + * giaddr. + */ + if (!packet->agent_options_stashed && + (packet->options != NULL) && + packet->options->universe_count > agent_universe.index && + packet->options->universes[agent_universe.index] != NULL) { + oc = lookup_option (&server_universe, state -> options, + SV_STASH_AGENT_OPTIONS); + if (!oc || + evaluate_boolean_option_cache (&ignorep, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (lt -> agent_options) + option_chain_head_dereference (< -> agent_options, MDL); + option_chain_head_reference + (< -> agent_options, + (struct option_chain_head *) + packet -> options -> universes [agent_universe.index], + MDL); + } + } + + /* Replace the old lease hostname with the new one, if it's changed. */ + oc = lookup_option (&dhcp_universe, packet -> options, DHO_HOST_NAME); + if (oc) + s1 = evaluate_option_cache (&d1, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, + (struct option_state *)0, + &global_scope, oc, MDL); + else + s1 = 0; + + if (oc && s1 && + lease -> client_hostname && + strlen (lease -> client_hostname) == d1.len && + !memcmp (lease -> client_hostname, d1.data, d1.len)) { + /* Hasn't changed. */ + data_string_forget (&d1, MDL); + lt -> client_hostname = lease -> client_hostname; + lease -> client_hostname = (char *)0; + } else if (oc && s1) { + lt -> client_hostname = dmalloc (d1.len + 1, MDL); + if (!lt -> client_hostname) + log_error ("no memory for client hostname."); + else { + memcpy (lt -> client_hostname, d1.data, d1.len); + lt -> client_hostname [d1.len] = 0; + } + data_string_forget (&d1, MDL); + } + + /* Record the hardware address, if given... */ + lt -> hardware_addr.hlen = packet -> raw -> hlen + 1; + lt -> hardware_addr.hbuf [0] = packet -> raw -> htype; + memcpy (< -> hardware_addr.hbuf [1], packet -> raw -> chaddr, + sizeof packet -> raw -> chaddr); + + lt -> flags = lease -> flags & ~PERSISTENT_FLAGS; + + /* If there are statements to execute when the lease is + committed, execute them. */ + if (lease -> on_commit && (!offer || offer == DHCPACK)) { + execute_statements ((struct binding_value **)0, + packet, lt, (struct client_state *)0, + packet -> options, + state -> options, < -> scope, + lease -> on_commit); + if (lease -> on_commit) + executable_statement_dereference (&lease -> on_commit, + MDL); + } + +#ifdef NSUPDATE + /* Perform DDNS updates, if configured to. */ + if ((!offer || offer == DHCPACK) && + (!(oc = lookup_option (&server_universe, state -> options, + SV_DDNS_UPDATES)) || + evaluate_boolean_option_cache (&ignorep, packet, lt, + (struct client_state *)0, + packet -> options, + state -> options, + < -> scope, oc, MDL))) { + ddns_updates(packet, lt, lease, NULL, NULL, state->options); + } +#endif /* NSUPDATE */ + + /* 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 + 1; + lease -> hardware_addr.hbuf [0] = packet -> raw -> htype; + memcpy (&lease -> hardware_addr.hbuf [1], + packet -> raw -> chaddr, + sizeof packet -> raw -> chaddr); /* XXX */ + } else { + +#if !defined(DELAYED_ACK) + /* Install the new information on 'lt' onto the lease at + * 'lease'. If this is a DHCPOFFER, it is a 'soft' promise, + * if it is a DHCPACK, it is a 'hard' binding, so it needs + * to be recorded and propogated immediately. If the update + * fails, don't ACK it (or BOOTREPLY) either; we may give + * the same lease to another client later, and that would be + * a conflict. + */ + if (!supersede_lease(lease, lt, !offer || (offer == DHCPACK), + offer == DHCPACK, offer == DHCPACK)) { +#else /* defined(DELAYED_ACK) */ + /* Install the new information on 'lt' onto the lease at + * 'lease'. We will not 'commit' this information to disk + * yet (fsync()), we will 'propogate' the information if + * this is BOOTP or a DHCPACK, but we will not 'pimmediate'ly + * transmit failover binding updates (this is delayed until + * after the fsync()). If the update fails, don't ACK it (or + * BOOTREPLY either); we may give the same lease out to a + * different client, and that would be a conflict. + */ + if (!supersede_lease(lease, lt, 0, !offer || offer == DHCPACK, + 0)) { +#endif + log_info ("%s: database update failed", msg); + free_lease_state (state, MDL); + lease_dereference (<, MDL); + return; + } + } + lease_dereference (<, MDL); + + /* Remember the interface on which the packet arrived. */ + state -> ip = packet -> interface; + + /* 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; + + /* 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, state -> options, + SV_ALWAYS_BROADCAST)) && + evaluate_boolean_option_cache (&ignorep, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL)) + state -> bootp_flags |= htons (BOOTP_BROADCAST); + + /* Get the Maximum Message Size option from the packet, if one + was sent. */ + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_MAX_MESSAGE_SIZE); + if (oc && + evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len == sizeof (u_int16_t)) + state -> max_message_size = getUShort (d1.data); + data_string_forget (&d1, MDL); + } else { + oc = lookup_option (&dhcp_universe, state -> options, + DHO_DHCP_MAX_MESSAGE_SIZE); + if (oc && + evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len == sizeof (u_int16_t)) + state -> max_message_size = + getUShort (d1.data); + data_string_forget (&d1, MDL); + } + } + + /* Get the Subnet Selection option from the packet, if one + was sent. */ + if ((oc = lookup_option (&dhcp_universe, packet -> options, + DHO_SUBNET_SELECTION))) { + + /* Make a copy of the data. */ + struct option_cache *noc = (struct option_cache *)0; + if (option_cache_allocate (&noc, MDL)) { + if (oc -> data.len) + data_string_copy (&noc -> data, + &oc -> data, MDL); + if (oc -> expression) + expression_reference (&noc -> expression, + oc -> expression, MDL); + if (oc -> option) + option_reference(&(noc->option), oc->option, + MDL); + } + + save_option (&dhcp_universe, state -> options, noc); + option_cache_dereference (&noc, MDL); + } + + /* Now, if appropriate, put in DHCP-specific options that + override those. */ + if (state -> offer) { + i = DHO_DHCP_MESSAGE_TYPE; + oc = (struct option_cache *)0; + if (option_cache_allocate (&oc, MDL)) { + if (make_const_data (&oc -> expression, + &state -> offer, 1, 0, 0, MDL)) { + option_code_hash_lookup(&oc->option, + dhcp_universe.code_hash, + &i, 0, MDL); + save_option (&dhcp_universe, + state -> options, oc); + } + option_cache_dereference (&oc, MDL); + } + + get_server_source_address(&from, state->options, packet); + memcpy(state->from.iabuf, &from, sizeof(from)); + state->from.len = sizeof(from); + + offered_lease_time = + state -> offered_expiry - cur_time; + + putULong(state->expiry, (u_int32_t)offered_lease_time); + i = DHO_DHCP_LEASE_TIME; + oc = (struct option_cache *)0; + if (option_cache_allocate (&oc, MDL)) { + if (make_const_data(&oc->expression, state->expiry, + 4, 0, 0, MDL)) { + option_code_hash_lookup(&oc->option, + dhcp_universe.code_hash, + &i, 0, MDL); + save_option (&dhcp_universe, + state -> options, oc); + } + option_cache_dereference (&oc, MDL); + } + + /* + * Validate any configured renew or rebinding times against + * the determined lease time. Do rebinding first so that + * the renew time can be validated against the rebind time. + */ + if ((oc = lookup_option(&dhcp_universe, state->options, + DHO_DHCP_REBINDING_TIME)) != NULL && + evaluate_option_cache(&d1, packet, lease, NULL, + packet->options, state->options, + &lease->scope, oc, MDL)) { + TIME rebind_time = getULong(d1.data); + + /* Drop the configured (invalid) rebinding time. */ + if (rebind_time >= offered_lease_time) + delete_option(&dhcp_universe, state->options, + DHO_DHCP_REBINDING_TIME); + else /* XXX: variable is reused. */ + offered_lease_time = rebind_time; + + data_string_forget(&d1, MDL); + } + + if ((oc = lookup_option(&dhcp_universe, state->options, + DHO_DHCP_RENEWAL_TIME)) != NULL && + evaluate_option_cache(&d1, packet, lease, NULL, + packet->options, state->options, + &lease->scope, oc, MDL)) { + if (getULong(d1.data) >= offered_lease_time) + delete_option(&dhcp_universe, state->options, + DHO_DHCP_RENEWAL_TIME); + + data_string_forget(&d1, MDL); + } + } else { + /* XXXSK: should we use get_server_source_address() here? */ + if (state -> ip -> address_count) { + state -> from.len = + sizeof state -> ip -> addresses [0]; + memcpy (state -> from.iabuf, + &state -> ip -> addresses [0], + state -> from.len); + } + } + + /* Figure out the address of the boot file server. */ + memset (&state -> siaddr, 0, sizeof state -> siaddr); + if ((oc = + lookup_option (&server_universe, + state -> options, SV_NEXT_SERVER))) { + if (evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL)) { + /* If there was more than one answer, + take the first. */ + if (d1.len >= 4 && d1.data) + memcpy (&state -> siaddr, d1.data, 4); + data_string_forget (&d1, MDL); + } + } + + /* Use the subnet mask from the subnet declaration if no other + mask has been provided. */ + i = DHO_SUBNET_MASK; + if (!lookup_option (&dhcp_universe, state -> options, i)) { + oc = (struct option_cache *)0; + 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, + state -> options, oc); + } + option_cache_dereference (&oc, MDL); + } + } + + /* Use the hostname from the host declaration if there is one + and no hostname has otherwise been provided, and if the + use-host-decl-name flag is set. */ + i = DHO_HOST_NAME; + j = SV_USE_HOST_DECL_NAMES; + if (!lookup_option (&dhcp_universe, state -> options, i) && + lease -> host && lease -> host -> name && + (evaluate_boolean_option_cache + (&ignorep, packet, lease, (struct client_state *)0, + packet -> options, state -> options, &lease -> scope, + lookup_option (&server_universe, state -> options, j), MDL))) { + oc = (struct option_cache *)0; + if (option_cache_allocate (&oc, MDL)) { + if (make_const_data (&oc -> expression, + ((unsigned char *) + lease -> host -> name), + strlen (lease -> host -> name), + 1, 0, MDL)) { + option_code_hash_lookup(&oc->option, + dhcp_universe.code_hash, + &i, 0, MDL); + save_option (&dhcp_universe, + state -> options, oc); + } + option_cache_dereference (&oc, MDL); + } + } + + /* If we don't have a hostname yet, and we've been asked to do + a reverse lookup to find the hostname, do it. */ + i = DHO_HOST_NAME; + j = SV_GET_LEASE_HOSTNAMES; + if (!lookup_option(&dhcp_universe, state->options, i) && + evaluate_boolean_option_cache + (&ignorep, packet, lease, NULL, + packet->options, state->options, &lease->scope, + lookup_option (&server_universe, state->options, j), MDL)) { + struct in_addr ia; + struct hostent *h; + + memcpy (&ia, lease -> ip_addr.iabuf, 4); + + h = gethostbyaddr ((char *)&ia, sizeof ia, AF_INET); + if (!h) + log_error ("No hostname for %s", inet_ntoa (ia)); + else { + oc = (struct option_cache *)0; + if (option_cache_allocate (&oc, MDL)) { + if (make_const_data (&oc -> expression, + ((unsigned char *) + h -> h_name), + strlen (h -> h_name) + 1, + 1, 1, MDL)) { + option_code_hash_lookup(&oc->option, + dhcp_universe.code_hash, + &i, 0, MDL); + save_option (&dhcp_universe, + state -> options, oc); + } + option_cache_dereference (&oc, MDL); + } + } + } + + /* 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. */ + + if (evaluate_boolean_option_cache + (&ignorep, packet, lease, (struct client_state *)0, + packet -> options, state -> options, &lease -> scope, + lookup_option (&server_universe, state -> options, + SV_USE_LEASE_ADDR_FOR_DEFAULT_ROUTE), MDL)) { + i = DHO_ROUTERS; + oc = lookup_option (&dhcp_universe, state -> options, i); + if (!oc) { + oc = (struct option_cache *)0; + if (option_cache_allocate (&oc, MDL)) { + if (make_const_data (&oc -> expression, + lease -> ip_addr.iabuf, + lease -> ip_addr.len, + 0, 0, MDL)) { + option_code_hash_lookup(&oc->option, + dhcp_universe.code_hash, + &i, 0, MDL); + save_option (&dhcp_universe, + state -> options, oc); + } + option_cache_dereference (&oc, MDL); + } + } + } + + /* If a site option space has been specified, use that for + site option codes. */ + i = SV_SITE_OPTION_SPACE; + if ((oc = lookup_option (&server_universe, state -> options, i)) && + evaluate_option_cache (&d1, packet, lease, + (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL)) { + struct universe *u = (struct universe *)0; + + if (!universe_hash_lookup (&u, universe_hash, + (const char *)d1.data, d1.len, + MDL)) { + log_error ("unknown option space %s.", d1.data); + return; + } + + state -> options -> site_universe = u -> index; + state->options->site_code_min = find_min_site_code(u); + data_string_forget (&d1, MDL); + } else { + state -> options -> site_code_min = 0; + state -> options -> site_universe = dhcp_universe.index; + } + + /* If the client has provided a list of options that it wishes + returned, use it to prioritize. If there's a parameter + request list in scope, use that in preference. Otherwise + use the default priority list. */ + + oc = lookup_option (&dhcp_universe, state -> options, + DHO_DHCP_PARAMETER_REQUEST_LIST); + + if (!oc) + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_PARAMETER_REQUEST_LIST); + if (oc) + evaluate_option_cache (&state -> parameter_request_list, + packet, lease, (struct client_state *)0, + packet -> options, state -> options, + &lease -> scope, oc, MDL); + +#ifdef DEBUG_PACKET + dump_packet (packet); + dump_raw ((unsigned char *)packet -> raw, packet -> packet_length); +#endif + + lease -> state = state; + + log_info ("%s", msg); + + /* Hang the packet off the lease state. */ + packet_reference (&lease -> state -> packet, packet, MDL); + + /* If this is a DHCPOFFER, ping the lease address before actually + sending the offer. */ + if (offer == DHCPOFFER && !(lease -> flags & STATIC_LEASE) && + ((cur_time - lease_cltt) > 60) && + (!(oc = lookup_option (&server_universe, state -> options, + SV_PING_CHECKS)) || + evaluate_boolean_option_cache (&ignorep, packet, lease, + (struct client_state *)0, + packet -> options, + state -> options, + &lease -> scope, oc, MDL))) { + icmp_echorequest (&lease -> ip_addr); + + /* Determine whether to use configured or default ping timeout. + */ + if ((oc = lookup_option (&server_universe, state -> options, + SV_PING_TIMEOUT)) && + evaluate_option_cache (&d1, packet, lease, NULL, + packet -> options, + state -> options, + &lease -> scope, oc, MDL)) { + if (d1.len == sizeof (u_int32_t)) + ping_timeout = getULong (d1.data); + else + ping_timeout = DEFAULT_PING_TIMEOUT; + + data_string_forget (&d1, MDL); + } else + ping_timeout = DEFAULT_PING_TIMEOUT; + +#ifdef DEBUG + log_debug ("Ping timeout: %ld", (long)ping_timeout); +#endif + + /* + * Set a timeout for 'ping-timeout' seconds from NOW, including + * current microseconds. As ping-timeout defaults to 1, the + * exclusion of current microseconds causes a value somewhere + * /between/ zero and one. + */ + tv.tv_sec = cur_tv.tv_sec + ping_timeout; + tv.tv_usec = cur_tv.tv_usec; + add_timeout (&tv, lease_ping_timeout, lease, + (tvref_t)lease_reference, + (tvunref_t)lease_dereference); + ++outstanding_pings; + } else { + lease->cltt = cur_time; +#if defined(DELAYED_ACK) + if (!(lease->flags & STATIC_LEASE) && + (!offer || (offer == DHCPACK))) + delayed_ack_enqueue(lease); + else +#endif + dhcp_reply(lease); + } +} + +/* + * CC: queue single ACK: + * - write the lease (but do not fsync it yet) + * - add to double linked list + * - commit if more than xx ACKs pending + * - if necessary set the max timer and bump the next timer + * but only up to the max timer value. + */ + +void +delayed_ack_enqueue(struct lease *lease) +{ + struct leasequeue *q; + + if (!write_lease(lease)) + return; + if (free_ackqueue) { + q = free_ackqueue; + free_ackqueue = q->next; + } else { + q = ((struct leasequeue *) + dmalloc(sizeof(struct leasequeue), MDL)); + if (!q) + log_fatal("delayed_ack_enqueue: no memory!"); + } + memset(q, 0, sizeof *q); + /* prepend to ackqueue*/ + lease_reference(&q->lease, lease, MDL); + q->next = ackqueue_head; + ackqueue_head = q; + if (!ackqueue_tail) + ackqueue_tail = q; + else + q->next->prev = q; + + outstanding_acks++; + if (outstanding_acks > max_outstanding_acks) { + commit_leases(); + + /* Reset max_fsync and cancel any pending timeout. */ + memset(&max_fsync, 0, sizeof(max_fsync)); + cancel_timeout(commit_leases_ackout, NULL); + } else { + struct timeval next_fsync; + + if (max_fsync.tv_sec == 0 && max_fsync.tv_usec == 0) { + /* set the maximum time we'll wait */ + max_fsync.tv_sec = cur_tv.tv_sec + max_ack_delay_secs; + max_fsync.tv_usec = cur_tv.tv_usec + + max_ack_delay_usecs; + + if (max_fsync.tv_usec >= 1000000) { + max_fsync.tv_sec++; + max_fsync.tv_usec -= 1000000; + } + } + + /* Set the timeout */ + next_fsync.tv_sec = cur_tv.tv_sec; + next_fsync.tv_usec = cur_tv.tv_usec + min_ack_delay_usecs; + if (next_fsync.tv_usec >= 1000000) { + next_fsync.tv_sec++; + next_fsync.tv_usec -= 1000000; + } + /* but not more than the max */ + if ((next_fsync.tv_sec > max_fsync.tv_sec) || + ((next_fsync.tv_sec == max_fsync.tv_sec) && + (next_fsync.tv_usec > max_fsync.tv_usec))) { + next_fsync.tv_sec = max_fsync.tv_sec; + next_fsync.tv_usec = max_fsync.tv_usec; + } + + add_timeout(&next_fsync, commit_leases_ackout, NULL, + (tvref_t) NULL, (tvunref_t) NULL); + } +} + +static void +commit_leases_ackout(void *foo) +{ + if (outstanding_acks) { + commit_leases(); + + memset(&max_fsync, 0, sizeof(max_fsync)); + } +} + +/* CC: process the delayed ACK responses: + - send out the ACK packets + - move the queue slots to the free list + */ +void +flush_ackqueue(void *foo) +{ + struct leasequeue *ack, *p; + /* process from bottom to retain packet order */ + for (ack = ackqueue_tail ; ack ; ack = p) { + p = ack->prev; + + /* dhcp_reply() requires that the reply state still be valid */ + if (ack->lease->state == NULL) + log_error("delayed ack for %s has gone stale", + piaddr(ack->lease->ip_addr)); + else + dhcp_reply(ack->lease); + + lease_dereference(&ack->lease, MDL); + ack->next = free_ackqueue; + free_ackqueue = ack; + } + ackqueue_head = NULL; + ackqueue_tail = NULL; + outstanding_acks = 0; +} + +#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) +void +relinquish_ackqueue(void) +{ + struct leasequeue *q, *n; + + for (q = ackqueue_head ; q ; q = n) { + n = q->next; + dfree(q, MDL); + } + for (q = free_ackqueue ; q ; q = n) { + n = q->next; + dfree(q, MDL); + } +} +#endif + +void dhcp_reply (lease) + struct lease *lease; +{ + int bufs = 0; + unsigned packet_length; + struct dhcp_packet raw; + struct sockaddr_in to; + struct in_addr from; + struct hardware hto; + int result; + struct lease_state *state = lease -> state; + int nulltp, bootpp, unicastp = 1; + struct data_string d1; + const char *s; + + if (!state) + log_fatal ("dhcp_reply was supplied lease with no state!"); + + /* Compose a response for the client... */ + memset (&raw, 0, sizeof raw); + memset (&d1, 0, sizeof d1); + + /* Copy in the filename if given; otherwise, flag the filename + buffer as available for options. */ + if (state -> filename.len && state -> filename.data) { + memcpy (raw.file, + state -> filename.data, + state -> filename.len > sizeof raw.file + ? sizeof raw.file : state -> filename.len); + if (sizeof raw.file > state -> filename.len) + memset (&raw.file [state -> filename.len], 0, + (sizeof raw.file) - state -> filename.len); + else + log_info("file name longer than packet field " + "truncated - field: %lu name: %d %.*s", + (unsigned long)sizeof(raw.file), + state->filename.len, state->filename.len, + state->filename.data); + } else + bufs |= 1; + + /* Copy in the server name if given; otherwise, flag the + server_name buffer as available for options. */ + if (state -> server_name.len && state -> server_name.data) { + memcpy (raw.sname, + state -> server_name.data, + state -> server_name.len > sizeof raw.sname + ? sizeof raw.sname : state -> server_name.len); + if (sizeof raw.sname > state -> server_name.len) + memset (&raw.sname [state -> server_name.len], 0, + (sizeof raw.sname) - state -> server_name.len); + else + log_info("server name longer than packet field " + "truncated - field: %lu name: %d %.*s", + (unsigned long)sizeof(raw.sname), + state->server_name.len, + state->server_name.len, + state->server_name.data); + } else + bufs |= 2; /* XXX */ + + memcpy (raw.chaddr, + &lease -> hardware_addr.hbuf [1], sizeof raw.chaddr); + raw.hlen = lease -> hardware_addr.hlen - 1; + raw.htype = lease -> hardware_addr.hbuf [0]; + + /* 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; + + /* Insert such options as will fit into the buffer. */ + packet_length = cons_options (state -> packet, &raw, lease, + (struct client_state *)0, + state -> max_message_size, + state -> packet -> options, + state -> options, &global_scope, + bufs, nulltp, bootpp, + &state -> parameter_request_list, + (char *)0); + + memcpy (&raw.ciaddr, &state -> ciaddr, sizeof raw.ciaddr); + memcpy (&raw.yiaddr, lease -> ip_addr.iabuf, 4); + raw.siaddr = state -> siaddr; + raw.giaddr = state -> giaddr; + + raw.xid = state -> xid; + raw.secs = state -> secs; + raw.flags = state -> bootp_flags; + raw.hops = state -> hops; + raw.op = BOOTREPLY; + + if (lease -> client_hostname) { + if ((strlen (lease -> client_hostname) <= 64) && + db_printable((unsigned char *)lease->client_hostname)) + s = lease -> client_hostname; + else + s = "Hostname Unsuitable for Printing"; + } else + s = (char *)0; + + /* Say what we're doing... */ + log_info ("%s on %s to %s %s%s%svia %s", + (state -> offer + ? (state -> offer == DHCPACK ? "DHCPACK" : "DHCPOFFER") + : "BOOTREPLY"), + piaddr (lease -> ip_addr), + (lease -> hardware_addr.hlen + ? print_hw_addr (lease -> hardware_addr.hbuf [0], + lease -> hardware_addr.hlen - 1, + &lease -> hardware_addr.hbuf [1]) + : print_hex_1(lease->uid_len, lease->uid, 60)), + s ? "(" : "", s ? s : "", s ? ") " : "", + (state -> giaddr.s_addr + ? inet_ntoa (state -> giaddr) + : state -> ip -> name)); + + /* Set up the hardware address... */ + hto.hlen = lease -> hardware_addr.hlen; + memcpy (hto.hbuf, lease -> hardware_addr.hbuf, hto.hlen); + + to.sin_family = AF_INET; +#ifdef HAVE_SA_LEN + to.sin_len = sizeof to; +#endif + memset (to.sin_zero, 0, sizeof to.sin_zero); + +#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; + if (raw.giaddr.s_addr != htonl (INADDR_LOOPBACK)) + to.sin_port = local_port; + else + to.sin_port = remote_port; /* For debugging. */ + + if (fallback_interface) { + result = send_packet (fallback_interface, + (struct packet *)0, + &raw, packet_length, + raw.siaddr, &to, + (struct hardware *)0); + + free_lease_state (state, MDL); + 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 -> subnet -> 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, MDL); + 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)) && + can_unicast_without_arp (state -> ip)) { + 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; + if (!(lease -> flags & UNICAST_BROADCAST_HACK)) + unicastp = 0; + } + + memcpy (&from, state -> from.iabuf, sizeof from); + + result = send_packet (state -> ip, + (struct packet *)0, &raw, packet_length, + from, &to, + unicastp ? &hto : (struct hardware *)0); + + /* Free all of the entries in the option_state structure + now that we're done with them. */ + + free_lease_state (state, MDL); + lease -> state = (struct lease_state *)0; +} + +int find_lease (struct lease **lp, + struct packet *packet, struct shared_network *share, int *ours, + int *peer_has_leases, struct lease *ip_lease_in, + const char *file, int line) +{ + struct lease *uid_lease = (struct lease *)0; + struct lease *ip_lease = (struct lease *)0; + struct lease *hw_lease = (struct lease *)0; + struct lease *lease = (struct lease *)0; + struct iaddr cip; + struct host_decl *hp = (struct host_decl *)0; + struct host_decl *host = (struct host_decl *)0; + struct lease *fixed_lease = (struct lease *)0; + struct lease *next = (struct lease *)0; + struct option_cache *oc; + struct data_string d1; + int have_client_identifier = 0; + struct data_string client_identifier; + struct hardware h; + +#if defined(FAILOVER_PROTOCOL) + /* Quick check to see if the peer has leases. */ + if (peer_has_leases) { + struct pool *pool; + + for (pool = share->pools ; pool ; pool = pool->next) { + dhcp_failover_state_t *peer = pool->failover_peer; + + if (peer && + ((peer->i_am == primary && pool->backup_leases) || + (peer->i_am == secondary && pool->free_leases))) { + *peer_has_leases = 1; + break; + } + } + } +#endif /* FAILOVER_PROTOCOL */ + + if (packet -> raw -> ciaddr.s_addr) { + cip.len = 4; + memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4); + } else { + /* Look up the requested address. */ + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_REQUESTED_ADDRESS); + memset (&d1, 0, sizeof d1); + if (oc && + evaluate_option_cache (&d1, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, + (struct option_state *)0, + &global_scope, oc, MDL)) { + packet -> got_requested_address = 1; + cip.len = 4; + memcpy (cip.iabuf, d1.data, cip.len); + data_string_forget (&d1, MDL); + } else + cip.len = 0; + } + + /* Try to find a host or lease that's been assigned to the + specified unique client identifier. */ + oc = lookup_option (&dhcp_universe, packet -> options, + DHO_DHCP_CLIENT_IDENTIFIER); + memset (&client_identifier, 0, sizeof client_identifier); + if (oc && + evaluate_option_cache (&client_identifier, + packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, (struct option_state *)0, + &global_scope, oc, MDL)) { + /* Remember this for later. */ + have_client_identifier = 1; + + /* First, try to find a fixed host entry for the specified + client identifier... */ + if (find_hosts_by_uid (&hp, client_identifier.data, + client_identifier.len, MDL)) { + /* Remember if we know of this client. */ + packet -> known = 1; + mockup_lease (&fixed_lease, packet, share, hp); + } + +#if defined (DEBUG_FIND_LEASE) + if (fixed_lease) { + log_info ("Found host for client identifier: %s.", + piaddr (fixed_lease -> ip_addr)); + } +#endif + if (hp) { + if (!fixed_lease) /* Save the host if we found one. */ + host_reference (&host, hp, MDL); + host_dereference (&hp, MDL); + } + + find_lease_by_uid (&uid_lease, client_identifier.data, + client_identifier.len, MDL); + } + + /* If we didn't find a fixed lease using the uid, try doing + it with the hardware address... */ + if (!fixed_lease && !host) { + if (find_hosts_by_haddr (&hp, packet -> raw -> htype, + packet -> raw -> chaddr, + packet -> raw -> hlen, MDL)) { + /* Remember if we know of this client. */ + packet -> known = 1; + if (host) + host_dereference (&host, MDL); + host_reference (&host, hp, MDL); + host_dereference (&hp, MDL); + mockup_lease (&fixed_lease, packet, share, host); +#if defined (DEBUG_FIND_LEASE) + if (fixed_lease) { + log_info ("Found host for link address: %s.", + piaddr (fixed_lease -> ip_addr)); + } +#endif + } + } + + /* Finally, if we haven't found anything yet try again with the + * host-identifier option ... */ + if (!fixed_lease && !host) { + if (find_hosts_by_option(&hp, packet, + packet->options, MDL) == 1) { + packet->known = 1; + if (host) + host_dereference(&host, MDL); + host_reference(&host, hp, MDL); + host_dereference(&hp, MDL); + mockup_lease (&fixed_lease, packet, share, host); +#if defined (DEBUG_FIND_LEASE) + if (fixed_lease) { + log_info ("Found host via host-identifier"); + } +#endif + } + } + + /* 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; + strcpy (dhcp_message, "requested address is incorrect"); +#if defined (DEBUG_FIND_LEASE) + log_info ("Client's fixed-address %s doesn't match %s%s", + piaddr (fixed_lease -> ip_addr), "request ", + print_dotted_quads (cip.len, cip.iabuf)); +#endif + goto out; + } + + /* + * If we found leases matching the client identifier, loop through + * the n_uid pointer looking for one that's actually valid. We + * can't do this until we get here because we depend on + * packet -> known, which may be set by either the uid host + * lookup or the haddr host lookup. + * + * Note that the n_uid lease chain is sorted in order of + * preference, so the first one is the best one. + */ + while (uid_lease) { +#if defined (DEBUG_FIND_LEASE) + log_info ("trying next lease matching client id: %s", + piaddr (uid_lease -> ip_addr)); +#endif + +#if defined (FAILOVER_PROTOCOL) + /* + * When we lookup a lease by uid, we know the client identifier + * matches the lease's record. If it is active, or was last + * active with the same client, we can trivially extend it. + * If is not or was not active, we can allocate it to this + * client if it matches the usual free/backup criteria (which + * is contained in lease_mine_to_reallocate()). + */ + if (uid_lease->binding_state != FTS_ACTIVE && + uid_lease->rewind_binding_state != FTS_ACTIVE && + !lease_mine_to_reallocate(uid_lease)) { +#if defined (DEBUG_FIND_LEASE) + log_info("not active or not mine to allocate: %s", + piaddr(uid_lease->ip_addr)); +#endif + goto n_uid; + } +#endif + + if (uid_lease -> subnet -> shared_network != share) { +#if defined (DEBUG_FIND_LEASE) + log_info ("wrong network segment: %s", + piaddr (uid_lease -> ip_addr)); +#endif + goto n_uid; + } + + if ((uid_lease -> pool -> prohibit_list && + permitted (packet, uid_lease -> pool -> prohibit_list)) || + (uid_lease -> pool -> permit_list && + !permitted (packet, uid_lease -> pool -> permit_list))) { +#if defined (DEBUG_FIND_LEASE) + log_info ("not permitted: %s", + piaddr (uid_lease -> ip_addr)); +#endif + n_uid: + if (uid_lease -> n_uid) + lease_reference (&next, + uid_lease -> n_uid, MDL); + if (!packet -> raw -> ciaddr.s_addr) + release_lease (uid_lease, packet); + lease_dereference (&uid_lease, MDL); + if (next) { + lease_reference (&uid_lease, next, MDL); + lease_dereference (&next, MDL); + } + continue; + } + break; + } +#if defined (DEBUG_FIND_LEASE) + if (uid_lease) + log_info ("Found lease for client id: %s.", + piaddr (uid_lease -> ip_addr)); +#endif + + /* Find a lease whose hardware address matches, whose client + * identifier matches (or equally doesn't have one), that's + * permitted, and that's on the correct subnet. + * + * Note that the n_hw chain is sorted in order of preference, so + * the first one found is the best one. + */ + h.hlen = packet -> raw -> hlen + 1; + h.hbuf [0] = packet -> raw -> htype; + memcpy (&h.hbuf [1], packet -> raw -> chaddr, packet -> raw -> hlen); + find_lease_by_hw_addr (&hw_lease, h.hbuf, h.hlen, MDL); + while (hw_lease) { +#if defined (DEBUG_FIND_LEASE) + log_info ("trying next lease matching hw addr: %s", + piaddr (hw_lease -> ip_addr)); +#endif +#if defined (FAILOVER_PROTOCOL) + /* + * When we lookup a lease by chaddr, we know the MAC address + * matches the lease record (we will check if the lease has a + * client-id the client does not next). If the lease is + * currently active or was last active with this client, we can + * trivially extend it. Otherwise, there are a set of rules + * that govern if we can reallocate this lease to any client + * ("lease_mine_to_reallocate()") including this one. + */ + if (hw_lease->binding_state != FTS_ACTIVE && + hw_lease->rewind_binding_state != FTS_ACTIVE && + !lease_mine_to_reallocate(hw_lease)) { +#if defined (DEBUG_FIND_LEASE) + log_info("not active or not mine to allocate: %s", + piaddr(hw_lease->ip_addr)); +#endif + goto n_hw; + } +#endif + + /* + * This conditional skips "potentially active" leases (leases + * we think are expired may be extended by the peer, etc) that + * may be assigned to a differently /client-identified/ client + * with the same MAC address. + */ + if (hw_lease -> binding_state != FTS_FREE && + hw_lease -> binding_state != FTS_BACKUP && + hw_lease -> uid && + (!have_client_identifier || + hw_lease -> uid_len != client_identifier.len || + memcmp (hw_lease -> uid, client_identifier.data, + hw_lease -> uid_len))) { +#if defined (DEBUG_FIND_LEASE) + log_info ("wrong client identifier: %s", + piaddr (hw_lease -> ip_addr)); +#endif + goto n_hw; + } + if (hw_lease -> subnet -> shared_network != share) { +#if defined (DEBUG_FIND_LEASE) + log_info ("wrong network segment: %s", + piaddr (hw_lease -> ip_addr)); +#endif + goto n_hw; + } + if ((hw_lease -> pool -> prohibit_list && + permitted (packet, hw_lease -> pool -> prohibit_list)) || + (hw_lease -> pool -> permit_list && + !permitted (packet, hw_lease -> pool -> permit_list))) { +#if defined (DEBUG_FIND_LEASE) + log_info ("not permitted: %s", + piaddr (hw_lease -> ip_addr)); +#endif + if (!packet -> raw -> ciaddr.s_addr) + release_lease (hw_lease, packet); + n_hw: + if (hw_lease -> n_hw) + lease_reference (&next, hw_lease -> n_hw, MDL); + lease_dereference (&hw_lease, MDL); + if (next) { + lease_reference (&hw_lease, next, MDL); + lease_dereference (&next, MDL); + } + continue; + } + break; + } +#if defined (DEBUG_FIND_LEASE) + if (hw_lease) + log_info ("Found lease for hardware address: %s.", + piaddr (hw_lease -> ip_addr)); +#endif + + /* Try to find a lease that's been allocated to the client's + IP address. */ + if (ip_lease_in) + lease_reference (&ip_lease, ip_lease_in, MDL); + else if (cip.len) + find_lease_by_ip_addr (&ip_lease, cip, MDL); + +#if defined (DEBUG_FIND_LEASE) + if (ip_lease) + log_info ("Found lease for requested address: %s.", + piaddr (ip_lease -> ip_addr)); +#endif + + /* 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 -> subnet -> shared_network != share)) { + if (ours) + *ours = 1; +#if defined (DEBUG_FIND_LEASE) + log_info ("...but it was on the wrong shared network."); +#endif + strcpy (dhcp_message, "requested address on bad subnet"); + lease_dereference (&ip_lease, MDL); + } + + /* + * If the requested address is in use (or potentially in use) by + * a different client, it can't be granted. + * + * This first conditional only detects if the lease is currently + * identified to a different client (client-id and/or chaddr + * mismatch). In this case we may not want to give the client the + * lease, if doing so may potentially be an addressing conflict. + */ + if (ip_lease && + (ip_lease -> uid ? + (!have_client_identifier || + ip_lease -> uid_len != client_identifier.len || + memcmp (ip_lease -> uid, client_identifier.data, + ip_lease -> uid_len)) : + (ip_lease -> hardware_addr.hbuf [0] != packet -> raw -> htype || + ip_lease -> hardware_addr.hlen != packet -> raw -> hlen + 1 || + memcmp (&ip_lease -> hardware_addr.hbuf [1], + packet -> raw -> chaddr, + (unsigned)(ip_lease -> hardware_addr.hlen - 1))))) { + /* + * A lease is unavailable for allocation to a new client if + * it is not in the FREE or BACKUP state. There may be + * leases that are in the expired state with a rewinding + * state that is free or backup, but these will be processed + * into the free or backup states by expiration processes, so + * checking for them here is superfluous. + */ + if (ip_lease -> binding_state != FTS_FREE && + ip_lease -> binding_state != FTS_BACKUP) { +#if defined (DEBUG_FIND_LEASE) + log_info ("rejecting lease for requested address."); +#endif + /* If we're rejecting it because the peer has + it, don't set "ours", because we shouldn't NAK. */ + if (ours && ip_lease -> binding_state != FTS_ACTIVE) + *ours = 0; + lease_dereference (&ip_lease, MDL); + } + } + + /* + * If we got an ip_lease and a uid_lease or hw_lease, and ip_lease + * is/was not active, and is not ours to reallocate, forget about it. + */ + if (ip_lease && (uid_lease || hw_lease) && + ip_lease->binding_state != FTS_ACTIVE && + ip_lease->rewind_binding_state != FTS_ACTIVE && +#if defined(FAILOVER_PROTOCOL) + !lease_mine_to_reallocate(ip_lease) && +#endif + packet->packet_type == DHCPDISCOVER) { +#if defined (DEBUG_FIND_LEASE) + log_info("ip lease not active or not ours to offer."); +#endif + lease_dereference(&ip_lease, MDL); + } + + /* If for some reason the client has more than one lease + on the subnet that matches its uid, pick the one that + it asked for and (if we can) free the other. */ + if (ip_lease && ip_lease->binding_state == FTS_ACTIVE && + ip_lease->uid && ip_lease != uid_lease) { + if (have_client_identifier && + (ip_lease -> uid_len == client_identifier.len) && + !memcmp (client_identifier.data, + ip_lease -> uid, ip_lease -> uid_len)) { + if (uid_lease) { + if (uid_lease->binding_state == FTS_ACTIVE) { + log_error ("client %s has duplicate%s on %s", + (print_hw_addr + (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr)), + " leases", + (ip_lease -> subnet -> + shared_network -> name)); + + /* If the client is REQUESTing the lease, + it shouldn't still be using the old + one, so we can free it for allocation. */ + if (uid_lease && + uid_lease->binding_state == FTS_ACTIVE && + !packet -> raw -> ciaddr.s_addr && + (share == + uid_lease -> subnet -> shared_network) && + packet -> packet_type == DHCPREQUEST) + release_lease (uid_lease, packet); + } + lease_dereference (&uid_lease, MDL); + lease_reference (&uid_lease, ip_lease, MDL); + } + } + + /* 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) { + lease_dereference (&fixed_lease, MDL); + db_conflict: + log_error ("Dynamic and static leases present for %s.", + piaddr (cip)); + log_error ("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)); + log_error ("from the dynamic address pool for %s", + ip_lease -> subnet -> shared_network -> name + ); + if (fixed_lease) + lease_dereference (&ip_lease, MDL); + strcpy (dhcp_message, + "database conflict - call for help!"); + } + + if (ip_lease && ip_lease != uid_lease) { +#if defined (DEBUG_FIND_LEASE) + log_info ("requested address not available."); +#endif + lease_dereference (&ip_lease, MDL); + } + } + + /* 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 extra pointers to the same lease... */ + if (hw_lease && hw_lease == uid_lease) { +#if defined (DEBUG_FIND_LEASE) + log_info ("hardware lease and uid lease are identical."); +#endif + lease_dereference (&hw_lease, MDL); + } + if (ip_lease && ip_lease == hw_lease) { + lease_dereference (&hw_lease, MDL); +#if defined (DEBUG_FIND_LEASE) + log_info ("hardware lease and ip lease are identical."); +#endif + } + if (ip_lease && ip_lease == uid_lease) { + lease_dereference (&uid_lease, MDL); +#if defined (DEBUG_FIND_LEASE) + log_info ("uid lease and ip lease are identical."); +#endif + } + + /* Make sure the client is permitted to use the requested lease. */ + if (ip_lease && + ((ip_lease -> pool -> prohibit_list && + permitted (packet, ip_lease -> pool -> prohibit_list)) || + (ip_lease -> pool -> permit_list && + !permitted (packet, ip_lease -> pool -> permit_list)))) { + if (!packet->raw->ciaddr.s_addr && + (ip_lease->binding_state == FTS_ACTIVE)) + release_lease (ip_lease, packet); + + lease_dereference (&ip_lease, MDL); + } + + if (uid_lease && + ((uid_lease -> pool -> prohibit_list && + permitted (packet, uid_lease -> pool -> prohibit_list)) || + (uid_lease -> pool -> permit_list && + !permitted (packet, uid_lease -> pool -> permit_list)))) { + if (!packet -> raw -> ciaddr.s_addr) + release_lease (uid_lease, packet); + lease_dereference (&uid_lease, MDL); + } + + if (hw_lease && + ((hw_lease -> pool -> prohibit_list && + permitted (packet, hw_lease -> pool -> prohibit_list)) || + (hw_lease -> pool -> permit_list && + !permitted (packet, hw_lease -> pool -> permit_list)))) { + if (!packet -> raw -> ciaddr.s_addr) + release_lease (hw_lease, packet); + lease_dereference (&hw_lease, MDL); + } + + /* 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) { + strcpy (dhcp_message, "requested address not available"); + } + + /* 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) { +#if defined (DEBUG_FIND_LEASE) + log_info ("no applicable lease found for DHCPREQUEST."); +#endif + goto out; + } + + /* At this point, if fixed_lease is nonzero, we can assign it to + this client. */ + if (fixed_lease) { + lease_reference (&lease, fixed_lease, MDL); + lease_dereference (&fixed_lease, MDL); +#if defined (DEBUG_FIND_LEASE) + log_info ("choosing fixed address."); +#endif + } + + /* 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 -> raw -> ciaddr.s_addr) + release_lease (ip_lease, packet); +#if defined (DEBUG_FIND_LEASE) + log_info ("not choosing requested address (!)."); +#endif + } else { +#if defined (DEBUG_FIND_LEASE) + log_info ("choosing lease on requested address."); +#endif + lease_reference (&lease, ip_lease, MDL); + if (lease -> host) + host_dereference (&lease -> host, MDL); + } + lease_dereference (&ip_lease, MDL); + } + + /* 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) { + log_error("uid lease %s for client %s is duplicate " + "on %s", + piaddr(uid_lease->ip_addr), + print_hw_addr(packet->raw->htype, + packet->raw->hlen, + packet->raw->chaddr), + uid_lease->subnet->shared_network->name); + + if (!packet -> raw -> ciaddr.s_addr && + packet -> packet_type == DHCPREQUEST && + uid_lease -> binding_state == FTS_ACTIVE) + release_lease(uid_lease, packet); +#if defined (DEBUG_FIND_LEASE) + log_info ("not choosing uid lease."); +#endif + } else { + lease_reference (&lease, uid_lease, MDL); + if (lease -> host) + host_dereference (&lease -> host, MDL); +#if defined (DEBUG_FIND_LEASE) + log_info ("choosing uid lease."); +#endif + } + lease_dereference (&uid_lease, MDL); + } + + /* The lease that matched the hardware address is treated likewise. */ + if (hw_lease) { + if (lease) { +#if defined (DEBUG_FIND_LEASE) + log_info ("not choosing hardware lease."); +#endif + } else { + /* We're a little lax here - if the client didn't + send a client identifier and it's a bootp client, + but the lease has a client identifier, we still + let the client have a lease. */ + if (!hw_lease -> uid_len || + (have_client_identifier + ? (hw_lease -> uid_len == + client_identifier.len && + !memcmp (hw_lease -> uid, + client_identifier.data, + client_identifier.len)) + : packet -> packet_type == 0)) { + lease_reference (&lease, hw_lease, MDL); + if (lease -> host) + host_dereference (&lease -> host, MDL); +#if defined (DEBUG_FIND_LEASE) + log_info ("choosing hardware lease."); +#endif + } else { +#if defined (DEBUG_FIND_LEASE) + log_info ("not choosing hardware lease: %s.", + "uid mismatch"); +#endif + } + } + lease_dereference (&hw_lease, MDL); + } + + /* + * 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) { + struct host_decl *p = NULL; + struct host_decl *n = NULL; + + host_reference(&p, host, MDL); + while (p != NULL) { + if (!p->fixed_addr) { + /* + * If the lease is currently active, then it + * must be allocated to the present client. + * We store a reference to the host record on + * the lease to save a lookup later (in + * ack_lease()). We mustn't refer to the host + * record on non-active leases because the + * client may be denied later. + * + * XXX: Not having this reference (such as in + * DHCPDISCOVER/INIT) means ack_lease will have + * to perform this lookup a second time. This + * hopefully isn't a problem as DHCPREQUEST is + * more common than DHCPDISCOVER. + */ + if (lease->binding_state == FTS_ACTIVE) + host_reference(&lease->host, p, MDL); + + host_dereference(&p, MDL); + break; + } + if (p->n_ipaddr != NULL) + host_reference(&n, p->n_ipaddr, MDL); + host_dereference(&p, MDL); + if (n != NULL) { + host_reference(&p, n, MDL); + host_dereference(&n, MDL); + } + } + } + + /* If we find an abandoned lease, but it's the one the client + requested, we assume that previous bugginess on the part + of the client, or a server database loss, caused the lease to + be abandoned, so we reclaim it and let the client have it. */ + if (lease && + (lease -> binding_state == FTS_ABANDONED) && + lease == ip_lease && + packet -> packet_type == DHCPREQUEST) { + log_error ("Reclaiming REQUESTed abandoned IP address %s.", + piaddr (lease -> ip_addr)); + } else if (lease && (lease -> binding_state == FTS_ABANDONED)) { + /* Otherwise, if it's not the one the client requested, we do not + return it - instead, we claim it's ours, causing a DHCPNAK to be + sent if this lookup is for a DHCPREQUEST, and force the client + to go back through the allocation process. */ + if (ours) + *ours = 1; + lease_dereference (&lease, MDL); + } + + out: + if (have_client_identifier) + data_string_forget (&client_identifier, MDL); + + if (fixed_lease) + lease_dereference (&fixed_lease, MDL); + if (hw_lease) + lease_dereference (&hw_lease, MDL); + if (uid_lease) + lease_dereference (&uid_lease, MDL); + if (ip_lease) + lease_dereference (&ip_lease, MDL); + if (host) + host_dereference (&host, MDL); + + if (lease) { +#if defined (DEBUG_FIND_LEASE) + log_info ("Returning lease: %s.", + piaddr (lease -> ip_addr)); +#endif + lease_reference (lp, lease, file, line); + lease_dereference (&lease, MDL); + return 1; + } +#if defined (DEBUG_FIND_LEASE) + log_info ("Not returning a lease."); +#endif + return 0; +} + +/* 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. */ + +int mockup_lease (struct lease **lp, struct packet *packet, + struct shared_network *share, struct host_decl *hp) +{ + struct lease *lease = (struct lease *)0; + struct host_decl *rhp = (struct host_decl *)0; + + if (lease_allocate (&lease, MDL) != ISC_R_SUCCESS) + return 0; + if (host_reference (&rhp, hp, MDL) != ISC_R_SUCCESS) { + lease_dereference (&lease, MDL); + return 0; + } + if (!find_host_for_network (&lease -> subnet, + &rhp, &lease -> ip_addr, share)) { + lease_dereference (&lease, MDL); + host_dereference (&rhp, MDL); + return 0; + } + host_reference (&lease -> host, rhp, MDL); + if (rhp -> client_identifier.len > sizeof lease -> uid_buf) + lease -> uid = dmalloc (rhp -> client_identifier.len, MDL); + else + lease -> uid = lease -> uid_buf; + if (!lease -> uid) { + lease_dereference (&lease, MDL); + host_dereference (&rhp, MDL); + return 0; + } + memcpy (lease -> uid, rhp -> client_identifier.data, + rhp -> client_identifier.len); + lease -> uid_len = rhp -> client_identifier.len; + lease -> hardware_addr = rhp -> interface; + lease -> starts = lease -> cltt = lease -> ends = MIN_TIME; + lease -> flags = STATIC_LEASE; + lease -> binding_state = FTS_FREE; + + lease_reference (lp, lease, MDL); + + lease_dereference (&lease, MDL); + host_dereference (&rhp, MDL); + return 1; +} + +/* Look through all the pools in a list starting with the specified pool + for a free lease. We try to find a virgin lease if we can. If we + don't find a virgin lease, we try to find a non-virgin lease that's + free. If we can't find one of those, we try to reclaim an abandoned + lease. If all of these possibilities fail to pan out, we don't return + a lease at all. */ + +int allocate_lease (struct lease **lp, struct packet *packet, + struct pool *pool, int *peer_has_leases) +{ + struct lease *lease = (struct lease *)0; + struct lease *candl = (struct lease *)0; + + for (; pool ; pool = pool -> next) { + if ((pool -> prohibit_list && + permitted (packet, pool -> prohibit_list)) || + (pool -> permit_list && + !permitted (packet, pool -> permit_list))) + continue; + +#if defined (FAILOVER_PROTOCOL) + /* Peer_has_leases just says that we found at least one + free lease. If no free lease is returned, the caller + can deduce that this means the peer is hogging all the + free leases, so we can print a better error message. */ + /* XXX Do we need code here to ignore PEER_IS_OWNER and + * XXX just check tstp if we're in, e.g., PARTNER_DOWN? + * XXX Where do we deal with CONFLICT_DETECTED, et al? */ + /* XXX This should be handled by the lease binding "state + * XXX machine" - that is, when we get here, if a lease + * XXX could be allocated, it will have the correct + * XXX binding state so that the following code will + * XXX result in its being allocated. */ + /* Skip to the most expired lease in the pool that is not + * owned by a failover peer. */ + if (pool->failover_peer != NULL) { + if (pool->failover_peer->i_am == primary) { + candl = pool->free; + + /* + * In normal operation, we never want to touch + * the peer's leases. In partner-down + * operation, we need to be able to pick up + * the peer's leases after STOS+MCLT. + */ + if (pool->backup != NULL) { + if (((candl == NULL) || + (candl->ends > + pool->backup->ends)) && + lease_mine_to_reallocate( + pool->backup)) { + candl = pool->backup; + } else { + *peer_has_leases = 1; + } + } + } else { + candl = pool->backup; + + if (pool->free != NULL) { + if (((candl == NULL) || + (candl->ends > + pool->free->ends)) && + lease_mine_to_reallocate( + pool->free)) { + candl = pool->free; + } else { + *peer_has_leases = 1; + } + } + } + + /* Try abandoned leases as a last resort. */ + if ((candl == NULL) && + (pool->abandoned != NULL) && + lease_mine_to_reallocate(pool->abandoned)) + candl = pool->abandoned; + } else +#endif + { + if (pool -> free) + candl = pool -> free; + else + candl = pool -> abandoned; + } + + /* + * XXX: This may not match with documented expectation. + * It's expected that when we OFFER a lease, we set its + * ends time forward 2 minutes so that it gets sorted to + * the end of its free list (avoiding a similar allocation + * to another client). It is not expected that we issue a + * "no free leases" error when the last lease has been + * offered, but it's not exactly broken either. + */ + if (!candl || (candl -> ends > cur_time)) + continue; + + if (!lease) { + lease = candl; + continue; + } + + /* + * There are tiers of lease state preference, listed here in + * reverse order (least to most preferential): + * + * ABANDONED + * FREE/BACKUP + * + * If the selected lease and candidate are both of the same + * state, select the oldest (longest ago) expiration time + * between the two. If the candidate lease is of a higher + * preferred grade over the selected lease, use it. + */ + if ((lease -> binding_state == FTS_ABANDONED) && + ((candl -> binding_state != FTS_ABANDONED) || + (candl -> ends < lease -> ends))) { + lease = candl; + continue; + } else if (candl -> binding_state == FTS_ABANDONED) + continue; + + if ((lease -> uid_len || lease -> hardware_addr.hlen) && + ((!candl -> uid_len && !candl -> hardware_addr.hlen) || + (candl -> ends < lease -> ends))) { + lease = candl; + continue; + } else if (candl -> uid_len || candl -> hardware_addr.hlen) + continue; + + if (candl -> ends < lease -> ends) + lease = candl; + } + + if (lease != NULL) { + if (lease->binding_state == FTS_ABANDONED) + log_error("Reclaiming abandoned lease %s.", + piaddr(lease->ip_addr)); + + /* + * XXX: For reliability, we go ahead and remove the host + * record and try to move on. For correctness, if there + * are any other stale host vectors, we want to find them. + */ + if (lease->host != NULL) { + log_debug("soft impossible condition (%s:%d): stale " + "host \"%s\" found on lease %s", MDL, + lease->host->name, + piaddr(lease->ip_addr)); + host_dereference(&lease->host, MDL); + } + + lease_reference (lp, lease, MDL); + return 1; + } + + return 0; +} + +/* Determine whether or not a permit exists on a particular permit list + that matches the specified packet, returning nonzero if so, zero if + not. */ + +int permitted (packet, permit_list) + struct packet *packet; + struct permit *permit_list; +{ + struct permit *p; + int i; + + for (p = permit_list; p; p = p -> next) { + switch (p -> type) { + case permit_unknown_clients: + if (!packet -> known) + return 1; + break; + + case permit_known_clients: + if (packet -> known) + return 1; + break; + + case permit_authenticated_clients: + if (packet -> authenticated) + return 1; + break; + + case permit_unauthenticated_clients: + if (!packet -> authenticated) + return 1; + break; + + case permit_all_clients: + return 1; + + case permit_dynamic_bootp_clients: + if (!packet -> options_valid || + !packet -> packet_type) + return 1; + break; + + case permit_class: + for (i = 0; i < packet -> class_count; i++) { + if (p -> class == packet -> classes [i]) + return 1; + if (packet -> classes [i] && + packet -> classes [i] -> superclass && + (packet -> classes [i] -> superclass == + p -> class)) + return 1; + } + break; + + case permit_after: + if (cur_time > p->after) + return 1; + break; + } + } + return 0; +} + +int locate_network (packet) + struct packet *packet; +{ + struct iaddr ia; + struct data_string data; + struct subnet *subnet = (struct subnet *)0; + struct option_cache *oc; + + /* See if there's a Relay Agent Link Selection Option, or a + * Subnet Selection Option. The Link-Select and Subnet-Select + * are formatted and used precisely the same, but we must prefer + * the link-select over the subnet-select. + */ + if ((oc = lookup_option(&agent_universe, packet->options, + RAI_LINK_SELECT)) == NULL) + oc = lookup_option(&dhcp_universe, packet->options, + DHO_SUBNET_SELECTION); + + /* If there's no SSO and no giaddr, then use the shared_network + from the interface, if there is one. If not, fail. */ + if (!oc && !packet -> raw -> giaddr.s_addr) { + if (packet -> interface -> shared_network) { + shared_network_reference + (&packet -> shared_network, + packet -> interface -> shared_network, MDL); + return 1; + } + return 0; + } + + /* If there's an option indicating link connection, and it's valid, + * use it to figure out the subnet. If it's not valid, fail. + */ + if (oc) { + memset (&data, 0, sizeof data); + if (!evaluate_option_cache (&data, packet, (struct lease *)0, + (struct client_state *)0, + packet -> options, + (struct option_state *)0, + &global_scope, oc, MDL)) { + return 0; + } + if (data.len != 4) { + return 0; + } + ia.len = 4; + memcpy (ia.iabuf, data.data, 4); + data_string_forget (&data, MDL); + } else { + ia.len = 4; + memcpy (ia.iabuf, &packet -> raw -> giaddr, 4); + } + + /* If we know the subnet on which the IP address lives, use it. */ + if (find_subnet (&subnet, ia, MDL)) { + shared_network_reference (&packet -> shared_network, + subnet -> shared_network, MDL); + subnet_dereference (&subnet, MDL); + return 1; + } + + /* Otherwise, fail. */ + return 0; +} + +/* + * Try to figure out the source address to send packets from. + * + * If the packet we received specified the server address, then we + * will use that. + * + * Otherwise, use the first address from the interface. If we do + * this, we also save this into the option cache as the server + * address. + */ +void +get_server_source_address(struct in_addr *from, + struct option_state *options, + struct packet *packet) { + unsigned option_num; + struct option_cache *oc; + struct data_string d; + struct in_addr *a; + + memset(&d, 0, sizeof(d)); + + option_num = DHO_DHCP_SERVER_IDENTIFIER; + oc = lookup_option(&dhcp_universe, options, option_num); + if (oc != NULL) { + if (evaluate_option_cache(&d, packet, NULL, NULL, + packet->options, options, + &global_scope, oc, MDL)) { + if (d.len == sizeof(*from)) { + memcpy(from, d.data, sizeof(*from)); + data_string_forget(&d, MDL); + return; + } + data_string_forget(&d, MDL); + } + oc = NULL; + } + + if (packet->interface->address_count > 0) { + if (option_cache_allocate(&oc, MDL)) { + a = &packet->interface->addresses[0]; + if (make_const_data(&oc->expression, + (unsigned char *)a, sizeof(*a), + 0, 0, MDL)) { + option_code_hash_lookup(&oc->option, + dhcp_universe.code_hash, + &option_num, 0, MDL); + save_option(&dhcp_universe, options, oc); + } + option_cache_dereference(&oc, MDL); + } + *from = packet->interface->addresses[0]; + } else { + memset(from, 0, sizeof(*from)); + } +} + +/* + * Look for the lowest numbered site code number and + * apply a log warning if it is less than 224. Do not + * permit site codes less than 128 (old code never did). + * + * Note that we could search option codes 224 down to 128 + * on the hash table, but the table is (probably) smaller + * than that if it was declared as a standalone table with + * defaults. So we traverse the option code hash. + */ +static int +find_min_site_code(struct universe *u) +{ + if (u->site_code_min) + return u->site_code_min; + + /* + * Note that site_code_min has to be global as we can't pass an + * argument through hash_foreach(). The value 224 is taken from + * RFC 3942. + */ + site_code_min = 224; + option_code_hash_foreach(u->code_hash, lowest_site_code); + + if (site_code_min < 224) { + log_error("WARNING: site-local option codes less than 224 have " + "been deprecated by RFC3942. You have options " + "listed in site local space %s that number as low as " + "%d. Please investigate if these should be declared " + "as regular options rather than site-local options, " + "or migrated up past 224.", + u->name, site_code_min); + } + + /* + * don't even bother logging, this is just silly, and never worked + * on any old version of software. + */ + if (site_code_min < 128) + site_code_min = 128; + + /* + * Cache the determined minimum site code on the universe structure. + * Note that due to the < 128 check above, a value of zero is + * impossible. + */ + u->site_code_min = site_code_min; + + return site_code_min; +} + +static isc_result_t +lowest_site_code(const void *key, unsigned len, void *object) +{ + struct option *option = object; + + if (option->code < site_code_min) + site_code_min = option->code; + + return ISC_R_SUCCESS; +} + +static void +maybe_return_agent_options(struct packet *packet, struct option_state *options) +{ + /* If there were agent options in the incoming packet, return + * them. Do not return the agent options if they were stashed + * on the lease. We do not check giaddr to detect the presence of + * a relay, as this excludes "l2" relay agents which have no giaddr + * to set. + * + * XXX: If the user configures options for the relay agent information + * (state->options->universes[agent_universe.index] is not NULL), + * we're still required to duplicate other values provided by the + * relay agent. So we need to merge the old values not configured + * by the user into the new state, not just give up. + */ + if (!packet->agent_options_stashed && + (packet->options != NULL) && + packet->options->universe_count > agent_universe.index && + packet->options->universes[agent_universe.index] != NULL && + (options->universe_count <= agent_universe.index || + options->universes[agent_universe.index] == NULL)) { + option_chain_head_reference + ((struct option_chain_head **) + &(options->universes[agent_universe.index]), + (struct option_chain_head *) + packet->options->universes[agent_universe.index], MDL); + + if (options->universe_count <= agent_universe.index) + options->universe_count = agent_universe.index + 1; + } +} |