#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dhcp.h"
#include "tree.h"
#include "dhcpd.h"
#include "log.h"
#include "sync.h"
int outstanding_pings;
static char dhcp_message[256];
void
dhcp(struct packet *packet, int is_udpsock)
{
if (!locate_network(packet) && packet->packet_type != DHCPREQUEST)
return;
if (is_udpsock && packet->packet_type != DHCPINFORM) {
log_info("Unable to handle a DHCP message type=%d on UDP "
"socket", packet->packet_type);
return;
}
switch (packet->packet_type) {
case DHCPDISCOVER:
dhcpdiscover(packet);
break;
case DHCPREQUEST:
dhcprequest(packet);
break;
case DHCPRELEASE:
dhcprelease(packet);
break;
case DHCPDECLINE:
dhcpdecline(packet);
break;
case DHCPINFORM:
dhcpinform(packet);
break;
default:
break;
}
}
void
dhcpdiscover(struct packet *packet)
{
struct lease *lease = find_lease(packet, packet->shared_network, 0);
struct host_decl *hp;
log_info("DHCPDISCOVER from %s via %s",
print_hw_addr(packet->raw->htype, packet->raw->hlen,
packet->raw->chaddr),
packet->raw->giaddr.s_addr ? inet_ntoa(packet->raw->giaddr) :
packet->interface->name);
if (!packet->shared_network) {
log_info("Packet from unknown subnet: %s",
inet_ntoa(packet->raw->giaddr));
return;
}
if (!lease) {
lease = packet->shared_network->last_lease;
if (!lease || lease->ends > cur_time) {
log_info("no free leases on subnet %s",
packet->shared_network->name);
return;
}
if ((lease->flags & ABANDONED_LEASE)) {
struct lease *lp;
for (lp = lease; lp; lp = lp->prev) {
if (lp->ends > cur_time)
break;
if (!(lp->flags & ABANDONED_LEASE)) {
lease = lp;
break;
}
}
if ((lease->flags & ABANDONED_LEASE)) {
log_warnx("Reclaiming abandoned IP address %s.",
piaddr(lease->ip_addr));
lease->flags &= ~ABANDONED_LEASE;
pfmsg('L', lease);
}
}
if (((packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len != 0) &&
((hp = find_hosts_by_uid(
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].data,
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len)) !=
NULL)) ||
((hp = find_hosts_by_haddr(packet->raw->htype,
packet->raw->chaddr, packet->raw->hlen)) != NULL)) {
for (; hp; hp = hp->n_ipaddr) {
if (!hp->fixed_addr) {
lease->host = hp;
break;
}
}
} else
lease->host = NULL;
}
if (!lease->host &&
!lease->subnet->group->boot_unknown_clients) {
log_info("Ignoring unknown client %s",
print_hw_addr(packet->raw->htype, packet->raw->hlen,
packet->raw->chaddr));
} else if (lease->host && !lease->host->group->allow_booting) {
log_info("Declining to boot client %s",
lease->host->name ? lease->host->name :
print_hw_addr(packet->raw->htype, packet->raw->hlen,
packet->raw->chaddr));
} else
ack_lease(packet, lease, DHCPOFFER, cur_time + 120);
}
void
dhcprequest(struct packet *packet)
{
struct lease *lease;
struct iaddr cip;
struct subnet *subnet;
int ours = 0;
cip.len = 4;
if (packet->options[DHO_DHCP_REQUESTED_ADDRESS].len == 4)
memcpy(cip.iabuf,
packet->options[DHO_DHCP_REQUESTED_ADDRESS].data, 4);
else
memcpy(cip.iabuf, &packet->raw->ciaddr.s_addr, 4);
subnet = find_subnet(cip);
if (subnet)
lease = find_lease(packet, subnet->shared_network, &ours);
else
lease = NULL;
log_info("DHCPREQUEST for %s from %s via %s", piaddr(cip),
print_hw_addr(packet->raw->htype, packet->raw->hlen,
packet->raw->chaddr),
packet->raw->giaddr.s_addr ? inet_ntoa(packet->raw->giaddr) :
packet->interface->name);
if (!packet->shared_network ||
(packet->raw->ciaddr.s_addr && packet->raw->giaddr.s_addr) ||
(packet->options[DHO_DHCP_REQUESTED_ADDRESS].len == 4 &&
!packet->raw->ciaddr.s_addr)) {
if (!packet->shared_network) {
if (subnet &&
subnet->shared_network->group->authoritative) {
nak_lease(packet, &cip);
return;
}
return;
}
subnet = find_grouped_subnet(packet->shared_network, cip);
if (!subnet) {
if (packet->shared_network->group->authoritative)
nak_lease(packet, &cip);
return;
}
}
if (lease && !addr_eq(lease->ip_addr, cip)) {
if (ours)
nak_lease(packet, &cip);
return;
}
if (!lease && ours) {
nak_lease(packet, &cip);
return;
}
if (lease && !lease->host &&
!lease->subnet->group->boot_unknown_clients) {
log_info("Ignoring unknown client %s",
print_hw_addr(packet->raw->htype, packet->raw->hlen,
packet->raw->chaddr));
return;
} else if (lease && lease->host && !lease->host->group->allow_booting)
{
log_info("Declining to renew client %s",
lease->host->name ? lease->host->name :
print_hw_addr(packet->raw->htype, packet->raw->hlen,
packet->raw->chaddr));
return;
}
if (lease &&
((lease->uid_len && lease->uid_len ==
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len &&
!memcmp(packet->options[DHO_DHCP_CLIENT_IDENTIFIER].data,
lease->uid, lease->uid_len)) ||
(lease->hardware_addr.hlen == packet->raw->hlen &&
lease->hardware_addr.htype == packet->raw->htype &&
!memcmp(lease->hardware_addr.haddr, packet->raw->chaddr,
packet->raw->hlen)))) {
ack_lease(packet, lease, DHCPACK, 0);
sync_lease(lease);
return;
}
if (lease) {
ack_lease(packet, lease, DHCPACK, 0);
sync_lease(lease);
}
}
void
dhcprelease(struct packet *packet)
{
char ciaddrbuf[INET_ADDRSTRLEN];
struct lease *lease;
struct iaddr cip;
int i;
if (packet->options[DHO_DHCP_REQUESTED_ADDRESS].len) {
log_info("DHCPRELEASE from %s specified requested-address.",
print_hw_addr(packet->raw->htype, packet->raw->hlen,
packet->raw->chaddr));
}
i = DHO_DHCP_CLIENT_IDENTIFIER;
if (packet->options[i].len) {
lease = find_lease_by_uid(packet->options[i].data,
packet->options[i].len);
for (; lease; lease = lease->n_uid) {
if (!memcmp(&packet->raw->ciaddr,
lease->ip_addr.iabuf, 4)) {
break;
}
}
} else {
cip.len = 4;
memcpy(cip.iabuf, &packet->raw->ciaddr, 4);
lease = find_lease_by_ip_addr(cip);
}
strlcpy(ciaddrbuf, inet_ntoa(packet->raw->ciaddr), sizeof(ciaddrbuf));
log_info("DHCPRELEASE of %s from %s via %s (%sfound)",
ciaddrbuf,
print_hw_addr(packet->raw->htype, packet->raw->hlen,
packet->raw->chaddr),
packet->raw->giaddr.s_addr ? inet_ntoa(packet->raw->giaddr) :
packet->interface->name,
lease ? "" : "not ");
if (lease && lease->state) {
log_info("DHCPRELEASE already acking lease %s",
piaddr(lease->ip_addr));
return;
}
if (lease && lease->ends > cur_time) {
if (!lease->releasing) {
log_info("DHCPRELEASE of %s from %s via %s (found)",
ciaddrbuf,
print_hw_addr(packet->raw->htype,
packet->raw->hlen, packet->raw->chaddr),
packet->raw->giaddr.s_addr ?
inet_ntoa(packet->raw->giaddr) :
packet->interface->name);
lease->releasing = 1;
add_timeout(cur_time + 1, lease_ping_timeout, lease);
icmp_echorequest(&(lease->ip_addr));
++outstanding_pings;
} else {
log_info("DHCPRELEASE of %s from %s via %s ignored "
"(release already pending)",
ciaddrbuf,
print_hw_addr(packet->raw->htype,
packet->raw->hlen, packet->raw->chaddr),
packet->raw->giaddr.s_addr ?
inet_ntoa(packet->raw->giaddr) :
packet->interface->name);
}
} else {
log_info("DHCPRELEASE of %s from %s via %s for nonexistent "
"lease", ciaddrbuf, print_hw_addr(packet->raw->htype,
packet->raw->hlen, packet->raw->chaddr),
packet->raw->giaddr.s_addr ?
inet_ntoa(packet->raw->giaddr) : packet->interface->name);
}
}
void
dhcpdecline(struct packet *packet)
{
struct lease *lease;
struct iaddr cip;
if (packet->options[DHO_DHCP_REQUESTED_ADDRESS].len != 4)
return;
cip.len = 4;
memcpy(cip.iabuf,
packet->options[DHO_DHCP_REQUESTED_ADDRESS].data, 4);
lease = find_lease_by_ip_addr(cip);
log_info("DHCPDECLINE on %s from %s via %s",
piaddr(cip), print_hw_addr(packet->raw->htype,
packet->raw->hlen, packet->raw->chaddr),
packet->raw->giaddr.s_addr ? inet_ntoa(packet->raw->giaddr) :
packet->interface->name);
if (lease && lease->state) {
log_info("DHCPDECLINE already acking lease %s",
piaddr(lease->ip_addr));
return;
}
if (lease)
abandon_lease(lease, "declined.");
}
void
dhcpinform(struct packet *packet)
{
struct lease lease;
struct iaddr cip;
struct subnet *subnet;
cip.len = 4;
if (packet->raw->ciaddr.s_addr && !packet->raw->giaddr.s_addr) {
if (memcmp(&packet->raw->ciaddr.s_addr,
packet->client_addr.iabuf, 4) != 0) {
log_info("DHCPINFORM from %s but ciaddr %s is not "
"consistent with actual address",
piaddr(packet->client_addr),
inet_ntoa(packet->raw->ciaddr));
return;
}
memcpy(cip.iabuf, &packet->raw->ciaddr.s_addr, 4);
} else
memcpy(cip.iabuf, &packet->client_addr.iabuf, 4);
log_info("DHCPINFORM from %s", piaddr(cip));
subnet = find_subnet(cip);
if (!subnet)
return;
if (!subnet->shared_network) {
log_info("Packet from unknown subnet: %s",
inet_ntoa(packet->raw->giaddr));
return;
}
memset(&lease, 0, sizeof(lease));
lease.subnet = subnet;
lease.shared_network = subnet->shared_network;
if (packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len)
lease.host = find_hosts_by_uid(
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].data,
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len);
lease.starts = lease.timestamp = lease.ends = MIN_TIME;
lease.flags = INFORM_NOLEASE;
ack_lease(packet, &lease, DHCPACK, 0);
if (lease.state != NULL)
free_lease_state(lease.state, "ack_lease");
}
void
nak_lease(struct packet *packet, struct iaddr *cip)
{
struct sockaddr_in to;
struct in_addr from;
ssize_t result;
int i;
struct dhcp_packet raw;
unsigned char nak = DHCPNAK;
struct packet outgoing;
struct tree_cache *options[256];
struct tree_cache dhcpnak_tree, dhcpmsg_tree;
struct tree_cache client_tree, server_tree;
memset(options, 0, sizeof options);
memset(&outgoing, 0, sizeof outgoing);
memset(&raw, 0, sizeof raw);
outgoing.raw = &raw;
i = DHO_DHCP_MESSAGE_TYPE;
options[i] = &dhcpnak_tree;
options[i]->value = &nak;
options[i]->len = sizeof nak;
options[i]->buf_size = sizeof nak;
options[i]->timeout = -1;
options[i]->tree = NULL;
options[i]->flags = 0;
i = DHO_DHCP_MESSAGE;
options[i] = &dhcpmsg_tree;
options[i]->value = (unsigned char *)dhcp_message;
options[i]->len = strlen(dhcp_message);
options[i]->buf_size = strlen(dhcp_message);
options[i]->timeout = -1;
options[i]->tree = NULL;
options[i]->flags = 0;
i = DHO_DHCP_SERVER_IDENTIFIER;
if (packet->options[i].len) {
options[i] = &server_tree;
options[i]->value = packet->options[i].data,
options[i]->len = packet->options[i].len;
options[i]->buf_size = packet->options[i].len;
options[i]->timeout = -1;
options[i]->tree = NULL;
options[i]->flags = 0;
}
i = DHO_DHCP_CLIENT_IDENTIFIER;
if (packet->options[i].len) {
options[i] = &client_tree;
options[i]->value = packet->options[i].data,
options[i]->len = packet->options[i].len;
options[i]->buf_size = packet->options[i].len;
options[i]->timeout = -1;
options[i]->tree = NULL;
options[i]->flags = 0;
}
i = DHO_DHCP_PARAMETER_REQUEST_LIST;
if (packet->options[i].data) {
packet->options[i].len = 0;
free(packet->options[i].data);
packet->options[i].data = NULL;
}
outgoing.packet_length = cons_options(packet, outgoing.raw,
0, options, 0, 0, 0, NULL, 0);
raw.siaddr = packet->interface->primary_address;
raw.giaddr = packet->raw->giaddr;
memcpy(raw.chaddr, packet->raw->chaddr, sizeof raw.chaddr);
raw.hlen = packet->raw->hlen;
raw.htype = packet->raw->htype;
raw.xid = packet->raw->xid;
raw.secs = packet->raw->secs;
raw.flags = packet->raw->flags | htons(BOOTP_BROADCAST);
raw.hops = packet->raw->hops;
raw.op = BOOTREPLY;
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);
memset(&to, 0, sizeof to);
to.sin_family = AF_INET;
to.sin_len = sizeof to;
from = packet->interface->primary_address;
if (outgoing.packet_length < BOOTP_MIN_LEN)
outgoing.packet_length = BOOTP_MIN_LEN;
if (raw.giaddr.s_addr) {
to.sin_addr = raw.giaddr;
to.sin_port = server_port;
result = packet->interface->send_packet(packet->interface, &raw,
outgoing.packet_length, from, &to, packet->haddr);
if (result == -1)
log_warn("send_fallback");
return;
} else {
to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
to.sin_port = client_port;
}
errno = 0;
result = packet->interface->send_packet(packet->interface, &raw,
outgoing.packet_length, from, &to, NULL);
}
void
ack_lease(struct packet *packet, struct lease *lease, unsigned int offer,
time_t when)
{
struct lease lt;
struct lease_state *state;
time_t lease_time, offered_lease_time, max_lease_time, default_lease_time;
struct class *vendor_class, *user_class;
int ulafdr, echo_client_id, i;
if (lease->state) {
if ((lease->flags & STATIC_LEASE) ||
cur_time - lease->timestamp < 60) {
log_info("already acking lease %s",
piaddr(lease->ip_addr));
return;
}
free_lease_state(lease->state, "ACK timed out");
lease->state = NULL;
}
i = DHO_DHCP_CLASS_IDENTIFIER;
if (packet->options[i].len) {
vendor_class = find_class(0, packet->options[i].data,
packet->options[i].len);
} else
vendor_class = NULL;
i = DHO_DHCP_USER_CLASS_ID;
if (packet->options[i].len) {
user_class = find_class(1, packet->options[i].data,
packet->options[i].len);
} else
user_class = NULL;
if (!lease->host) {
if (vendor_class && !vendor_class->group->allow_booting) {
log_debug("Booting denied by vendor class");
return;
}
if (user_class && !user_class->group->allow_booting) {
log_debug("Booting denied by user class");
return;
}
}
state = new_lease_state("ack_lease");
if (!state)
fatalx("unable to allocate lease state!");
memset(state, 0, sizeof *state);
state->got_requested_address = packet->got_requested_address;
state->shared_network = packet->interface->shared_network;
i = DHO_DHCP_SERVER_IDENTIFIER;
if (packet->options[i].len)
state->got_server_identifier = 1;
i = DHO_HOST_NAME;
if (packet->options[i].len && lease->client_hostname &&
(strlen(lease->client_hostname) == packet->options[i].len) &&
!memcmp(lease->client_hostname, packet->options[i].data,
packet->options[i].len)) {
} else if (packet->options[i].len) {
free(lease->client_hostname);
lease->client_hostname = malloc( packet->options[i].len + 1);
if (!lease->client_hostname)
fatalx("no memory for client hostname.\n");
memcpy(lease->client_hostname, packet->options[i].data,
packet->options[i].len);
lease->client_hostname[packet->options[i].len] = 0;
} else if (lease->client_hostname) {
free(lease->client_hostname);
lease->client_hostname = 0;
}
i = DHO_DHCP_CLIENT_IDENTIFIER;
if (packet->options[i].len && lease->client_identifier &&
lease->client_identifier_len == packet->options[i].len &&
!memcmp(lease->client_identifier, packet->options[i].data,
packet->options[i].len)) {
} else if (packet->options[i].len) {
free(lease->client_identifier);
lease->client_identifier = malloc(packet->options[i].len);
if (!lease->client_identifier)
fatalx("no memory for client identifier.\n");
lease->client_identifier_len = packet->options[i].len;
memcpy(lease->client_identifier, packet->options[i].data,
packet->options[i].len);
} else if (lease->client_identifier) {
free(lease->client_identifier);
lease->client_identifier = NULL;
lease->client_identifier_len = 0;
}
if (lease->host && lease->host->group->filename)
strlcpy(state->filename, lease->host->group->filename,
sizeof state->filename);
else if (user_class && user_class->group->filename)
strlcpy(state->filename, user_class->group->filename,
sizeof state->filename);
else if (vendor_class && vendor_class->group->filename)
strlcpy(state->filename, vendor_class->group->filename,
sizeof state->filename);
else if (packet->raw->file[0])
strlcpy(state->filename, packet->raw->file,
sizeof state->filename);
else if (lease->subnet->group->filename)
strlcpy(state->filename, lease->subnet->group->filename,
sizeof state->filename);
else
strlcpy(state->filename, "", sizeof state->filename);
if (lease->host && lease->host->group->server_name)
state->server_name = lease->host->group->server_name;
else if (user_class && user_class->group->server_name)
state->server_name = user_class->group->server_name;
else if (vendor_class && vendor_class->group->server_name)
state->server_name = vendor_class->group->server_name;
else if (lease->subnet->group->server_name)
state->server_name = lease->subnet->group->server_name;
else state->server_name = NULL;
memset(<, 0, sizeof lt);
lt.ip_addr = lease->ip_addr;
lt.starts = cur_time;
if (lease->host && lease->host->group->max_lease_time)
max_lease_time = lease->host->group->max_lease_time;
else
max_lease_time = lease->subnet->group->max_lease_time;
if (lease->host && lease->host->group->default_lease_time)
default_lease_time = lease->host->group->default_lease_time;
else
default_lease_time = lease->subnet->group->default_lease_time;
if (offer) {
i = DHO_DHCP_LEASE_TIME;
if (packet->options[i].len == 4) {
lease_time = getULong( packet->options[i].data);
if (lease_time < 1 || lease_time > max_lease_time)
lease_time = max_lease_time;
} else
lease_time = default_lease_time;
state->offered_expiry = cur_time + lease_time;
if (when)
lt.ends = when;
else
lt.ends = state->offered_expiry;
} else {
if (lease->host &&
lease->host->group->bootp_lease_length)
lt.ends = (cur_time +
lease->host->group->bootp_lease_length);
else if (lease->subnet->group->bootp_lease_length)
lt.ends = (cur_time +
lease->subnet->group->bootp_lease_length);
else if (lease->host &&
lease->host->group->bootp_lease_cutoff)
lt.ends = lease->host->group->bootp_lease_cutoff;
else
lt.ends = lease->subnet->group->bootp_lease_cutoff;
state->offered_expiry = lt.ends;
lt.flags = BOOTP_LEASE;
}
i = DHO_DHCP_CLIENT_IDENTIFIER;
if (packet->options[i].len) {
if (packet->options[i].len <= sizeof lt.uid_buf) {
memcpy(lt.uid_buf, packet->options[i].data,
packet->options[i].len);
lt.uid = lt.uid_buf;
lt.uid_max = sizeof lt.uid_buf;
lt.uid_len = packet->options[i].len;
} else {
lt.uid_max = lt.uid_len = packet->options[i].len;
lt.uid = malloc(lt.uid_max);
if (!lt.uid)
fatalx("can't allocate memory for large uid.");
memcpy(lt.uid, packet->options[i].data, lt.uid_len);
}
}
lt.host = lease->host;
lt.subnet = lease->subnet;
lt.shared_network = lease->shared_network;
if (lease->flags & (STATIC_LEASE | INFORM_NOLEASE)) {
lease->hardware_addr.hlen = packet->raw->hlen;
lease->hardware_addr.htype = packet->raw->htype;
memcpy(lease->hardware_addr.haddr, packet->raw->chaddr,
sizeof packet->raw->chaddr);
} else {
lt.hardware_addr.hlen = packet->raw->hlen;
lt.hardware_addr.htype = packet->raw->htype;
memcpy(lt.hardware_addr.haddr, packet->raw->chaddr,
sizeof packet->raw->chaddr);
if (!(supersede_lease(lease, <, !offer ||
offer == DHCPACK) || (offer && offer != DHCPACK))) {
free_lease_state(state, "ack_lease: !supersede_lease");
return;
}
}
state->ip = packet->interface;
i = DHO_HOST_NAME;
if (packet->options[i].len &&
packet->options[i].data[packet->options[i].len - 1] == '\0')
lease->flags |= MS_NULL_TERMINATION;
else
lease->flags &= ~MS_NULL_TERMINATION;
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;
memcpy(&state->haddr, packet->haddr, sizeof state->haddr);
memcpy(state->options, lease->subnet->group->options,
sizeof state->options);
if (state->offer) {
if (vendor_class) {
for (i = 0; i < 256; i++)
if (vendor_class->group->options[i])
state->options[i] =
vendor_class->group->options[i];
}
if (user_class) {
for (i = 0; i < 256; i++)
if (user_class->group->options[i])
state->options[i] =
user_class->group->options[i];
}
}
if (lease->host) {
for (i = 0; i < 256; i++)
if (lease->host->group->options[i])
state->options[i] =
lease->host->group->options[i];
}
i = DHO_DHCP_MAX_MESSAGE_SIZE;
if (packet->options[i].data &&
packet->options[i].len == sizeof(u_int16_t))
state->max_message_size = getUShort(packet->options[i].data);
else if (state->options[i] && state->options[i]->value)
state->max_message_size = getUShort(state->options[i]->value);
i = DHO_DHCP_PARAMETER_REQUEST_LIST;
if (packet->options[i].data) {
state->prl = calloc(1, packet->options[i].len);
if (!state->prl)
log_warnx("no memory for parameter request list");
else {
memcpy(state->prl, packet->options[i].data,
packet->options[i].len);
state->prl_len = packet->options[i].len;
}
}
i = DHO_HOST_NAME;
if (!state->options[i] && lease->hostname) {
state->options[i] = new_tree_cache("hostname");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value = (unsigned char *)lease->hostname;
state->options[i]->len = strlen(lease->hostname);
state->options[i]->buf_size = state->options[i]->len;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
}
if (state->offer) {
i = DHO_DHCP_MESSAGE_TYPE;
state->options[i] = new_tree_cache("message-type");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value = &state->offer;
state->options[i]->len = sizeof state->offer;
state->options[i]->buf_size = sizeof state->offer;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
i = DHO_DHCP_SERVER_IDENTIFIER;
if (!state->options[i]) {
use_primary:
state->options[i] = new_tree_cache("server-id");
state->options[i]->value =
(unsigned char *)&state->ip->primary_address;
state->options[i]->len =
sizeof state->ip->primary_address;
state->options[i]->buf_size = state->options[i]->len;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
state->from.len = sizeof state->ip->primary_address;
memcpy(state->from.iabuf, &state->ip->primary_address,
state->from.len);
} else {
if (!tree_evaluate(state->options[i]))
goto use_primary;
if (!state->options[i]->value ||
(state->options[i]->len >
sizeof state->from.iabuf))
goto use_primary;
state->from.len = state->options[i]->len;
memcpy(state->from.iabuf, state->options[i]->value,
state->from.len);
}
if (packet->options[i].len == 4) {
if (state->options[i]->len != 4 ||
memcmp(packet->options[i].data,
state->options[i]->value, 4) != 0) {
free_lease_state(state, "ack_lease: "
"server identifier");
return;
}
}
if (vendor_class) {
i = DHO_DHCP_CLASS_IDENTIFIER;
state->options[i] = new_tree_cache("class-identifier");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value =
(unsigned char *)vendor_class->name;
state->options[i]->len = strlen(vendor_class->name);
state->options[i]->buf_size = state->options[i]->len;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
}
if (user_class) {
i = DHO_DHCP_USER_CLASS_ID;
state->options[i] = new_tree_cache("user-class");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value =
(unsigned char *)user_class->name;
state->options[i]->len = strlen(user_class->name);
state->options[i]->buf_size = state->options[i]->len;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
}
}
if (state->offer && (lease->flags & INFORM_NOLEASE) == 0) {
if ((state->offered_expiry - cur_time) < 15)
offered_lease_time = default_lease_time;
else if (state->offered_expiry - cur_time > max_lease_time)
offered_lease_time = max_lease_time;
else
offered_lease_time =
state->offered_expiry - cur_time;
putULong((unsigned char *)&state->expiry, offered_lease_time);
i = DHO_DHCP_LEASE_TIME;
state->options[i] = new_tree_cache("lease-expiry");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value = (unsigned char *)&state->expiry;
state->options[i]->len = sizeof state->expiry;
state->options[i]->buf_size = sizeof state->expiry;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
offered_lease_time /= 2;
putULong((unsigned char *)&state->renewal, offered_lease_time);
i = DHO_DHCP_RENEWAL_TIME;
state->options[i] = new_tree_cache("renewal-time");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value =
(unsigned char *)&state->renewal;
state->options[i]->len = sizeof state->renewal;
state->options[i]->buf_size = sizeof state->renewal;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
offered_lease_time += (offered_lease_time / 2 +
offered_lease_time / 4);
putULong((unsigned char *)&state->rebind, offered_lease_time);
i = DHO_DHCP_REBINDING_TIME;
state->options[i] = new_tree_cache("rebind-time");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value = (unsigned char *)&state->rebind;
state->options[i]->len = sizeof state->rebind;
state->options[i]->buf_size = sizeof state->rebind;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
}
i = DHO_SUBNET_MASK;
if (!state->options[i]) {
state->options[i] = new_tree_cache("subnet-mask");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value = lease->subnet->netmask.iabuf;
state->options[i]->len = lease->subnet->netmask.len;
state->options[i]->buf_size = lease->subnet->netmask.len;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
}
ulafdr = 0;
if (lease->host) {
if (lease->host->group->use_lease_addr_for_default_route)
ulafdr = 1;
} else if (user_class) {
if (user_class->group->use_lease_addr_for_default_route)
ulafdr = 1;
} else if (vendor_class) {
if (vendor_class->group->use_lease_addr_for_default_route)
ulafdr = 1;
} else if (lease->subnet->group->use_lease_addr_for_default_route)
ulafdr = 1;
else
ulafdr = 0;
i = DHO_ROUTERS;
if (ulafdr && !state->options[i]) {
state->options[i] = new_tree_cache("routers");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value = lease->ip_addr.iabuf;
state->options[i]->len = lease->ip_addr.len;
state->options[i]->buf_size = lease->ip_addr.len;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
}
i = DHO_RELAY_AGENT_INFORMATION;
memset(&state->options[i], 0, sizeof(state->options[i]));
if (lease->host)
echo_client_id = lease->host->group->echo_client_id;
else if (user_class)
echo_client_id = user_class->group->echo_client_id;
else if (vendor_class)
echo_client_id = vendor_class->group->echo_client_id;
else
echo_client_id = lease->subnet->group->echo_client_id;
i = DHO_DHCP_CLIENT_IDENTIFIER;
if (lease->client_identifier && echo_client_id) {
state->options[i] = new_tree_cache("dhcp-client-identifier");
state->options[i]->flags = TC_TEMPORARY;
state->options[i]->value = lease->client_identifier;
state->options[i]->len = lease->client_identifier_len;
state->options[i]->buf_size = lease->client_identifier_len;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
} else
memset(&state->options[i], 0, sizeof(state->options[i]));
lease->state = state;
if (offer == DHCPOFFER && !(lease->flags & STATIC_LEASE) &&
cur_time - lease->timestamp > 60) {
lease->timestamp = cur_time;
icmp_echorequest(&lease->ip_addr);
add_timeout(cur_time + 1, lease_ping_timeout, lease);
++outstanding_pings;
} else {
lease->timestamp = cur_time;
dhcp_reply(lease);
}
}
void
dhcp_reply(struct lease *lease)
{
char ciaddrbuf[INET_ADDRSTRLEN];
int bufs = 0, packet_length, i;
struct dhcp_packet raw;
struct sockaddr_in to;
struct in_addr from;
struct lease_state *state = lease->state;
int nulltp, bootpp;
u_int8_t *prl;
int prl_len;
if (!state)
fatalx("dhcp_reply was supplied lease with no state!");
memset(&raw, 0, sizeof raw);
if (state->filename[0])
strlcpy(raw.file, state->filename, sizeof raw.file);
else
bufs |= 1;
if (state->server_name)
strlcpy(raw.sname, state->server_name, sizeof raw.sname);
else
bufs |= 2;
memcpy(raw.chaddr, lease->hardware_addr.haddr, sizeof raw.chaddr);
raw.hlen = lease->hardware_addr.hlen;
raw.htype = lease->hardware_addr.htype;
if (lease->flags & MS_NULL_TERMINATION)
nulltp = 1;
else
nulltp = 0;
if (state->offer)
bootpp = 0;
else
bootpp = 1;
if (state->options[DHO_DHCP_PARAMETER_REQUEST_LIST] &&
state->options[DHO_DHCP_PARAMETER_REQUEST_LIST]->value) {
prl = state->options[DHO_DHCP_PARAMETER_REQUEST_LIST]->value;
prl_len = state->options[DHO_DHCP_PARAMETER_REQUEST_LIST]->len;
} else if (state->prl) {
prl = state->prl;
prl_len = state->prl_len;
} else {
prl = NULL;
prl_len = 0;
}
packet_length = cons_options(NULL, &raw, state->max_message_size,
state->options, bufs, nulltp, bootpp, prl, prl_len);
for (i = 0; i < 256; i++) {
if (state->options[i] &&
state->options[i]->flags & TC_TEMPORARY)
free_tree_cache(state->options[i]);
}
memcpy(&raw.ciaddr, &state->ciaddr, sizeof raw.ciaddr);
if ((lease->flags & INFORM_NOLEASE) == 0)
memcpy(&raw.yiaddr, lease->ip_addr.iabuf, 4);
if (lease->host && lease->host->group->next_server.len)
memcpy(&raw.siaddr, lease->host->group->next_server.iabuf, 4);
else if (lease->subnet->group->next_server.len)
memcpy(&raw.siaddr, lease->subnet->group->next_server.iabuf,
4);
else if (lease->subnet->interface_address.len)
memcpy(&raw.siaddr, lease->subnet->interface_address.iabuf, 4);
else
raw.siaddr = state->ip->primary_address;
raw.giaddr = state->giaddr;
raw.xid = state->xid;
raw.secs = state->secs;
raw.flags = state->bootp_flags;
raw.hops = state->hops;
raw.op = BOOTREPLY;
strlcpy(ciaddrbuf, inet_ntoa(state->ciaddr), sizeof(ciaddrbuf));
if ((state->offer == DHCPACK) && (lease->flags & INFORM_NOLEASE))
log_info("DHCPACK to %s (%s) via %s",
ciaddrbuf,
print_hw_addr(lease->hardware_addr.htype,
lease->hardware_addr.hlen, lease->hardware_addr.haddr),
state->giaddr.s_addr ? inet_ntoa(state->giaddr) :
state->ip->name);
else
log_info("%s on %s to %s via %s",
(state->offer ? (state->offer == DHCPACK ? "DHCPACK" :
"DHCPOFFER") : "BOOTREPLY"),
piaddr(lease->ip_addr),
print_hw_addr(lease->hardware_addr.htype,
lease->hardware_addr.hlen, lease->hardware_addr.haddr),
state->giaddr.s_addr ? inet_ntoa(state->giaddr) :
state->ip->name);
memset(&to, 0, sizeof to);
to.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
to.sin_len = sizeof to;
#endif
if (packet_length < BOOTP_MIN_LEN)
packet_length = BOOTP_MIN_LEN;
if (raw.giaddr.s_addr) {
to.sin_addr = raw.giaddr;
to.sin_port = server_port;
memcpy(&from, state->from.iabuf, sizeof from);
(void) state->ip->send_packet(state->ip, &raw,
packet_length, from, &to, &state->haddr);
free_lease_state(state, "dhcp_reply gateway");
lease->state = NULL;
return;
} else if (raw.ciaddr.s_addr &&
!((state->got_server_identifier ||
(raw.flags & htons(BOOTP_BROADCAST))) &&
(state->shared_network == lease->shared_network)) &&
state->offer == DHCPACK) {
to.sin_addr = raw.ciaddr;
to.sin_port = client_port;
} else if (!(raw.flags & htons(BOOTP_BROADCAST))) {
to.sin_addr = raw.yiaddr;
to.sin_port = client_port;
} else {
to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
to.sin_port = client_port;
memset(&state->haddr, 0xff, sizeof state->haddr);
}
memcpy(&from, state->from.iabuf, sizeof from);
(void) state->ip->send_packet(state->ip, &raw, packet_length,
from, &to, &state->haddr);
free_lease_state(state, "dhcp_reply");
lease->state = NULL;
}
struct lease *
find_lease(struct packet *packet, struct shared_network *share,
int *ours)
{
struct lease *uid_lease, *ip_lease, *hw_lease;
struct lease *lease = NULL;
struct iaddr cip;
struct host_decl *hp, *host = NULL;
struct lease *fixed_lease;
if (packet->options[DHO_DHCP_REQUESTED_ADDRESS].len == 4) {
packet->got_requested_address = 1;
cip.len = 4;
memcpy(cip.iabuf,
packet->options[DHO_DHCP_REQUESTED_ADDRESS].data,
cip.len);
} else if (packet->raw->ciaddr.s_addr) {
cip.len = 4;
memcpy(cip.iabuf, &packet->raw->ciaddr, 4);
} else
cip.len = 0;
if (packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len) {
hp = find_hosts_by_uid(
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].data,
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len);
if (hp) {
host = hp;
fixed_lease = mockup_lease(packet, share, hp);
uid_lease = NULL;
} else {
uid_lease = find_lease_by_uid(
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].data,
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len);
for (; uid_lease; uid_lease = uid_lease->n_uid)
if (uid_lease->shared_network == share)
break;
fixed_lease = NULL;
if (uid_lease && (uid_lease->flags & ABANDONED_LEASE))
uid_lease = NULL;
}
} else {
uid_lease = NULL;
fixed_lease = NULL;
}
if (!fixed_lease) {
hp = find_hosts_by_haddr(packet->raw->htype,
packet->raw->chaddr, packet->raw->hlen);
if (hp) {
host = hp;
fixed_lease = mockup_lease(packet, share, hp);
}
}
if (packet->packet_type == DHCPREQUEST && fixed_lease &&
(fixed_lease->ip_addr.len != cip.len ||
memcmp(fixed_lease->ip_addr.iabuf, cip.iabuf, cip.len))) {
if (ours)
*ours = 1;
strlcpy(dhcp_message, "requested address is incorrect",
sizeof(dhcp_message));
return NULL;
}
hw_lease = find_lease_by_hw_addr(packet->raw->chaddr,
packet->raw->hlen);
for (; hw_lease; hw_lease = hw_lease->n_hw) {
if (hw_lease->shared_network == share) {
if ((hw_lease->flags & ABANDONED_LEASE))
continue;
if (packet->packet_type)
break;
if (hw_lease->flags &
(BOOTP_LEASE | DYNAMIC_BOOTP_OK))
break;
}
}
if (cip.len)
ip_lease = find_lease_by_ip_addr(cip);
else
ip_lease = NULL;
if (ip_lease && ours)
*ours = 1;
if (ip_lease && (ip_lease->shared_network != share)) {
ip_lease = NULL;
strlcpy(dhcp_message, "requested address on bad subnet",
sizeof(dhcp_message));
}
if (ip_lease && ip_lease->ends >= cur_time && ip_lease != uid_lease) {
int i = DHO_DHCP_CLIENT_IDENTIFIER;
if ((ip_lease->uid_len && packet->options[i].data &&
ip_lease->uid_len == packet->options[i].len &&
!memcmp(packet->options[i].data, ip_lease->uid,
ip_lease->uid_len)) ||
(!ip_lease->uid_len &&
ip_lease->hardware_addr.htype == packet->raw->htype &&
ip_lease->hardware_addr.hlen == packet->raw->hlen &&
!memcmp(ip_lease->hardware_addr.haddr, packet->raw->chaddr,
ip_lease->hardware_addr.hlen))) {
if (uid_lease) {
if (uid_lease->ends > cur_time) {
log_warnx("client %s has duplicate "
"leases on %s",
print_hw_addr(packet->raw->htype,
packet->raw->hlen,
packet->raw->chaddr),
ip_lease->shared_network->name);
if (uid_lease &&
!packet->raw->ciaddr.s_addr)
release_lease(uid_lease);
}
uid_lease = ip_lease;
}
} else {
strlcpy(dhcp_message, "requested address is not "
"available", sizeof(dhcp_message));
ip_lease = NULL;
}
if (packet->packet_type == DHCPREQUEST && fixed_lease) {
fixed_lease = NULL;
db_conflict:
log_warnx("Both dynamic and static leases present for "
"%s.", piaddr(cip));
log_warnx("Either remove host declaration %s or "
"remove %s", (fixed_lease && fixed_lease->host ?
(fixed_lease->host->name ?
fixed_lease->host->name : piaddr(cip)) :
piaddr(cip)), piaddr(cip));
log_warnx("from the dynamic address pool for %s",
share->name);
if (fixed_lease)
ip_lease = NULL;
strlcpy(dhcp_message, "database conflict - call for "
"help!", sizeof(dhcp_message));
}
}
if (packet->packet_type == DHCPREQUEST && fixed_lease && ip_lease)
goto db_conflict;
if (hw_lease && hw_lease->ends >= cur_time && hw_lease->uid &&
packet->options[DHO_DHCP_CLIENT_IDENTIFIER].len &&
hw_lease != uid_lease)
hw_lease = NULL;
if (hw_lease == uid_lease)
hw_lease = NULL;
if (ip_lease == hw_lease)
hw_lease = NULL;
if (ip_lease == uid_lease)
uid_lease = NULL;
if (!ip_lease) {
strlcpy(dhcp_message, "requested address not available",
sizeof(dhcp_message));
}
if (ip_lease && share != ip_lease->shared_network) {
if (packet->packet_type == DHCPREQUEST)
release_lease(ip_lease);
ip_lease = NULL;
}
if (uid_lease && share != uid_lease->shared_network) {
if (packet->packet_type == DHCPREQUEST)
release_lease(uid_lease);
uid_lease = NULL;
}
if (hw_lease && share != hw_lease->shared_network) {
if (packet->packet_type == DHCPREQUEST)
release_lease(hw_lease);
hw_lease = NULL;
}
if (packet->packet_type == DHCPREQUEST && !ip_lease && !fixed_lease)
return NULL;
if (fixed_lease)
lease = fixed_lease;
if (ip_lease) {
if (lease) {
if (packet->packet_type == DHCPREQUEST)
release_lease(ip_lease);
} else {
lease = ip_lease;
lease->host = NULL;
}
}
if (uid_lease) {
if (lease) {
if (packet->packet_type == DHCPREQUEST)
release_lease(uid_lease);
} else {
lease = uid_lease;
lease->host = NULL;
}
}
if (hw_lease) {
if (lease) {
if (packet->packet_type == DHCPREQUEST)
release_lease(hw_lease);
} else {
lease = hw_lease;
lease->host = NULL;
}
}
if (lease && host && !lease->host) {
for (; host; host = host->n_ipaddr) {
if (!host->fixed_addr) {
lease->host = host;
break;
}
}
}
if (lease && (lease->flags & ABANDONED_LEASE)) {
if (packet->packet_type == DHCPREQUEST) {
log_warnx("Reclaiming REQUESTed abandoned IP address "
"%s.", piaddr(lease->ip_addr));
lease->flags &= ~ABANDONED_LEASE;
} else
lease = NULL;
}
return lease;
}
struct lease *
mockup_lease(struct packet *packet, struct shared_network *share,
struct host_decl *hp)
{
static struct lease mock;
mock.subnet = find_host_for_network(&hp, &mock.ip_addr, share);
if (!mock.subnet)
return (NULL);
mock.next = mock.prev = NULL;
mock.shared_network = mock.subnet->shared_network;
mock.host = hp;
if (hp->group->options[DHO_DHCP_CLIENT_IDENTIFIER]) {
mock.uid =
hp->group->options[DHO_DHCP_CLIENT_IDENTIFIER]->value;
mock.uid_len =
hp->group->options[DHO_DHCP_CLIENT_IDENTIFIER]->len;
} else {
mock.uid = NULL;
mock.uid_len = 0;
}
mock.hardware_addr = hp->interface;
mock.starts = mock.timestamp = mock.ends = MIN_TIME;
mock.flags = STATIC_LEASE;
return &mock;
}