aboutsummaryrefslogtreecommitdiff
path: root/server/dhcpleasequery.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/dhcpleasequery.c')
-rw-r--r--server/dhcpleasequery.c1268
1 files changed, 1268 insertions, 0 deletions
diff --git a/server/dhcpleasequery.c b/server/dhcpleasequery.c
new file mode 100644
index 0000000..9daff89
--- /dev/null
+++ b/server/dhcpleasequery.c
@@ -0,0 +1,1268 @@
+/*
+ * Copyright (C) 2006-2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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.
+ */
+
+#include "dhcpd.h"
+
+/*
+ * TODO: RFC4388 specifies that the server SHOULD return the same
+ * options it would for a DHCREQUEST message, if no Parameter
+ * Request List option (option 55) is passed. We do not do that.
+ *
+ * TODO: RFC4388 specifies the creation of a "non-sensitive options"
+ * configuration list, and that these SHOULD be returned. We
+ * have no such list.
+ *
+ * TODO: RFC4388 says the server SHOULD use RFC3118, "Authentication
+ * for DHCP Messages".
+ *
+ * TODO: RFC4388 specifies that you SHOULD insure that you cannot be
+ * DoS'ed by DHCPLEASEQUERY message.
+ */
+
+/*
+ * If you query by hardware address or by client ID, then you may have
+ * more than one IP address for your query argument. We need to do two
+ * things:
+ *
+ * 1. Find the most recent lease.
+ * 2. Find all additional IP addresses for the query argument.
+ *
+ * We do this by looking through all of the leases associated with a
+ * given hardware address or client ID. We use the cltt (client last
+ * transaction time) of the lease, which only has a resolution of one
+ * second, so we might not actually give the very latest IP.
+ */
+
+static struct lease*
+next_hw(const struct lease *lease) {
+ /* INSIST(lease != NULL); */
+ return lease->n_hw;
+}
+
+static struct lease*
+next_uid(const struct lease *lease) {
+ /* INSIST(lease != NULL); */
+ return lease->n_uid;
+}
+
+void
+get_newest_lease(struct lease **retval,
+ struct lease *lease,
+ struct lease *(*next)(const struct lease *)) {
+
+ struct lease *p;
+ struct lease *newest;
+
+ /* INSIST(newest != NULL); */
+ /* INSIST(next != NULL); */
+
+ *retval = NULL;
+
+ if (lease == NULL) {
+ return;
+ }
+
+ newest = lease;
+ for (p=next(lease); p != NULL; p=next(p)) {
+ if (newest->binding_state == FTS_ACTIVE) {
+ if ((p->binding_state == FTS_ACTIVE) &&
+ (p->cltt > newest->cltt)) {
+ newest = p;
+ }
+ } else {
+ if (p->ends > newest->ends) {
+ newest = p;
+ }
+ }
+ }
+
+ lease_reference(retval, newest, MDL);
+}
+
+static int
+get_associated_ips(const struct lease *lease,
+ struct lease *(*next)(const struct lease *),
+ const struct lease *newest,
+ u_int32_t *associated_ips,
+ unsigned int associated_ips_size) {
+
+ const struct lease *p;
+ int cnt;
+
+ /* INSIST(next != NULL); */
+ /* INSIST(associated_ips != NULL); */
+
+ if (lease == NULL) {
+ return 0;
+ }
+
+ cnt = 0;
+ for (p=lease; p != NULL; p=next(p)) {
+ if ((p->binding_state == FTS_ACTIVE) && (p != newest)) {
+ if (cnt < associated_ips_size) {
+ memcpy(&associated_ips[cnt],
+ p->ip_addr.iabuf,
+ sizeof(associated_ips[cnt]));
+ }
+ cnt++;
+ }
+ }
+ return cnt;
+}
+
+
+void
+dhcpleasequery(struct packet *packet, int ms_nulltp) {
+ char msgbuf[256];
+ char dbg_info[128];
+ struct iaddr cip;
+ struct iaddr gip;
+ struct data_string uid;
+ struct hardware h;
+ struct lease *tmp_lease;
+ struct lease *lease;
+ int want_associated_ip;
+ int assoc_ip_cnt;
+ u_int32_t assoc_ips[40]; /* XXXSK: arbitrary maximum number of IPs */
+ const int nassoc_ips = sizeof(assoc_ips) / sizeof(assoc_ips[0]);
+
+ unsigned char dhcpMsgType;
+ const char *dhcp_msg_type_name;
+ struct subnet *subnet;
+ struct group *relay_group;
+ struct option_state *options;
+ struct option_cache *oc;
+ int allow_leasequery;
+ int ignorep;
+ u_int32_t lease_duration;
+ u_int32_t time_renewal;
+ u_int32_t time_rebinding;
+ u_int32_t time_expiry;
+ u_int32_t client_last_transaction_time;
+ struct sockaddr_in to;
+ struct in_addr siaddr;
+ struct data_string prl;
+ struct data_string *prl_ptr;
+
+ int i;
+ struct interface_info *interface;
+
+ /* INSIST(packet != NULL); */
+
+ /*
+ * Prepare log information.
+ */
+ snprintf(msgbuf, sizeof(msgbuf),
+ "DHCPLEASEQUERY from %s", inet_ntoa(packet->raw->giaddr));
+
+ /*
+ * We can't reply if there is no giaddr field.
+ */
+ if (!packet->raw->giaddr.s_addr) {
+ log_info("%s: missing giaddr, ciaddr is %s, no reply sent",
+ msgbuf, inet_ntoa(packet->raw->ciaddr));
+ return;
+ }
+
+ /*
+ * Initially we use the 'giaddr' subnet options scope to determine if
+ * the giaddr-identified relay agent is permitted to perform a
+ * leasequery. The subnet is not required, and may be omitted, in
+ * which case we are essentially interrogating the root options class
+ * to find a globally permit.
+ */
+ gip.len = sizeof(packet->raw->giaddr);
+ memcpy(gip.iabuf, &packet->raw->giaddr, sizeof(packet->raw->giaddr));
+
+ subnet = NULL;
+ find_subnet(&subnet, gip, MDL);
+ if (subnet != NULL)
+ relay_group = subnet->group;
+ else
+ relay_group = root_group;
+
+ subnet_dereference(&subnet, MDL);
+
+ options = NULL;
+ if (!option_state_allocate(&options, MDL)) {
+ log_error("No memory for option state.");
+ log_info("%s: out of memory, no reply sent", msgbuf);
+ return;
+ }
+
+ execute_statements_in_scope(NULL,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ relay_group,
+ NULL);
+
+ for (i=packet->class_count-1; i>=0; i--) {
+ execute_statements_in_scope(NULL,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ packet->classes[i]->group,
+ relay_group);
+ }
+
+ /*
+ * Because LEASEQUERY has some privacy concerns, default to deny.
+ */
+ allow_leasequery = 0;
+
+ /*
+ * See if we are authorized to do LEASEQUERY.
+ */
+ oc = lookup_option(&server_universe, options, SV_LEASEQUERY);
+ if (oc != NULL) {
+ allow_leasequery = evaluate_boolean_option_cache(&ignorep,
+ packet, NULL, NULL, packet->options,
+ options, &global_scope, oc, MDL);
+ }
+
+ if (!allow_leasequery) {
+ log_info("%s: LEASEQUERY not allowed, query ignored", msgbuf);
+ option_state_dereference(&options, MDL);
+ return;
+ }
+
+
+ /*
+ * Copy out the client IP address.
+ */
+ cip.len = sizeof(packet->raw->ciaddr);
+ memcpy(cip.iabuf, &packet->raw->ciaddr, sizeof(packet->raw->ciaddr));
+
+ /*
+ * If the client IP address is valid (not all zero), then we
+ * are looking for information about that IP address.
+ */
+ assoc_ip_cnt = 0;
+ lease = tmp_lease = NULL;
+ if (memcmp(cip.iabuf, "\0\0\0", 4)) {
+
+ want_associated_ip = 0;
+
+ snprintf(dbg_info, sizeof(dbg_info), "IP %s", piaddr(cip));
+ find_lease_by_ip_addr(&lease, cip, MDL);
+
+
+ } else {
+
+ want_associated_ip = 1;
+
+ /*
+ * If the client IP address is all zero, then we will
+ * either look up by the client identifier (if we have
+ * one), or by the MAC address.
+ */
+
+ memset(&uid, 0, sizeof(uid));
+ if (get_option(&uid,
+ &dhcp_universe,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ NULL,
+ packet->options,
+ &global_scope,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ MDL)) {
+
+ snprintf(dbg_info,
+ sizeof(dbg_info),
+ "client-id %s",
+ print_hex_1(uid.len, uid.data, 60));
+
+ find_lease_by_uid(&tmp_lease, uid.data, uid.len, MDL);
+ data_string_forget(&uid, MDL);
+ get_newest_lease(&lease, tmp_lease, next_uid);
+ assoc_ip_cnt = get_associated_ips(tmp_lease,
+ next_uid,
+ lease,
+ assoc_ips,
+ nassoc_ips);
+
+ } else {
+
+ if (packet->raw->hlen+1 > sizeof(h.hbuf)) {
+ log_info("%s: hardware length too long, "
+ "no reply sent", msgbuf);
+ option_state_dereference(&options, MDL);
+ return;
+ }
+
+ h.hlen = packet->raw->hlen + 1;
+ h.hbuf[0] = packet->raw->htype;
+ memcpy(&h.hbuf[1],
+ packet->raw->chaddr,
+ packet->raw->hlen);
+
+ snprintf(dbg_info,
+ sizeof(dbg_info),
+ "MAC address %s",
+ print_hw_addr(h.hbuf[0],
+ h.hlen - 1,
+ &h.hbuf[1]));
+
+ find_lease_by_hw_addr(&tmp_lease, h.hbuf, h.hlen, MDL);
+ get_newest_lease(&lease, tmp_lease, next_hw);
+ assoc_ip_cnt = get_associated_ips(tmp_lease,
+ next_hw,
+ lease,
+ assoc_ips,
+ nassoc_ips);
+
+ }
+
+ lease_dereference(&tmp_lease, MDL);
+
+ if (lease != NULL) {
+ memcpy(&packet->raw->ciaddr,
+ lease->ip_addr.iabuf,
+ sizeof(packet->raw->ciaddr));
+ }
+
+ /*
+ * Log if we have too many IP addresses associated
+ * with this client.
+ */
+ if (want_associated_ip && (assoc_ip_cnt > nassoc_ips)) {
+ log_info("%d IP addresses associated with %s, "
+ "only %d sent in reply.",
+ assoc_ip_cnt, dbg_info, nassoc_ips);
+ }
+ }
+
+ /*
+ * We now know the query target too, so can report this in
+ * our log message.
+ */
+ snprintf(msgbuf, sizeof(msgbuf),
+ "DHCPLEASEQUERY from %s for %s",
+ inet_ntoa(packet->raw->giaddr), dbg_info);
+
+ /*
+ * Figure our our return type.
+ */
+ if (lease == NULL) {
+ dhcpMsgType = DHCPLEASEUNKNOWN;
+ dhcp_msg_type_name = "DHCPLEASEUNKNOWN";
+ } else {
+ if (lease->binding_state == FTS_ACTIVE) {
+ dhcpMsgType = DHCPLEASEACTIVE;
+ dhcp_msg_type_name = "DHCPLEASEACTIVE";
+ } else {
+ dhcpMsgType = DHCPLEASEUNASSIGNED;
+ dhcp_msg_type_name = "DHCPLEASEUNASSIGNED";
+ }
+ }
+
+ /*
+ * Set options that only make sense if we have an active lease.
+ */
+
+ if (dhcpMsgType == DHCPLEASEACTIVE)
+ {
+ /*
+ * RFC 4388 uses the PRL to request options for the agent to
+ * receive that are "about" the client. It is confusing
+ * because in some cases it wants to know what was sent to
+ * the client (lease times, adjusted), and in others it wants
+ * to know information the client sent. You're supposed to
+ * know this on a case-by-case basis.
+ *
+ * "Name servers", "domain name", and the like from the relay
+ * agent's scope seems less than useful. Our options are to
+ * restart the option cache from the lease's best point of view
+ * (execute statements from the lease pool's group), or to
+ * simply restart the option cache from empty.
+ *
+ * I think restarting the option cache from empty best
+ * approaches RFC 4388's intent; specific options are included.
+ */
+ option_state_dereference(&options, MDL);
+
+ if (!option_state_allocate(&options, MDL)) {
+ log_error("%s: out of memory, no reply sent", msgbuf);
+ lease_dereference(&lease, MDL);
+ return;
+ }
+
+ /*
+ * Set the hardware address fields.
+ */
+
+ packet->raw->hlen = lease->hardware_addr.hlen - 1;
+ packet->raw->htype = lease->hardware_addr.hbuf[0];
+ memcpy(packet->raw->chaddr,
+ &lease->hardware_addr.hbuf[1],
+ sizeof(packet->raw->chaddr));
+
+ /*
+ * Set client identifier option.
+ */
+ if (lease->uid_len > 0) {
+ if (!add_option(options,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ lease->uid,
+ lease->uid_len)) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+
+ /*
+ * Calculate T1 and T2, the times when the client
+ * tries to extend its lease on its networking
+ * address.
+ * These seem to be hard-coded in ISC DHCP, to 0.5 and
+ * 0.875 of the lease time.
+ */
+
+ lease_duration = lease->ends - lease->starts;
+ time_renewal = lease->starts +
+ (lease_duration / 2);
+ time_rebinding = lease->starts +
+ (lease_duration / 2) +
+ (lease_duration / 4) +
+ (lease_duration / 8);
+
+ if (time_renewal > cur_time) {
+ if (time_renewal < cur_time)
+ time_renewal = 0;
+ else
+ time_renewal = htonl(time_renewal - cur_time);
+
+ if (!add_option(options,
+ DHO_DHCP_RENEWAL_TIME,
+ &time_renewal,
+ sizeof(time_renewal))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ if (time_rebinding > cur_time) {
+ time_rebinding = htonl(time_rebinding - cur_time);
+
+ if (!add_option(options,
+ DHO_DHCP_REBINDING_TIME,
+ &time_rebinding,
+ sizeof(time_rebinding))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ if (lease->ends > cur_time) {
+ if (time_expiry < cur_time) {
+ log_error("Impossible condition at %s:%d.",
+ MDL);
+
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ return;
+ }
+ time_expiry = htonl(lease->ends - cur_time);
+ if (!add_option(options,
+ DHO_DHCP_LEASE_TIME,
+ &time_expiry,
+ sizeof(time_expiry))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ /* Supply the Vendor-Class-Identifier. */
+ if (lease->scope != NULL) {
+ struct data_string vendor_class;
+
+ memset(&vendor_class, 0, sizeof(vendor_class));
+
+ if (find_bound_string(&vendor_class, lease->scope,
+ "vendor-class-identifier")) {
+ if (!add_option(options,
+ DHO_VENDOR_CLASS_IDENTIFIER,
+ (void *)vendor_class.data,
+ vendor_class.len)) {
+ option_state_dereference(&options,
+ MDL);
+ lease_dereference(&lease, MDL);
+ log_error("%s: error adding vendor "
+ "class identifier, no reply "
+ "sent", msgbuf);
+ data_string_forget(&vendor_class, MDL);
+ return;
+ }
+ data_string_forget(&vendor_class, MDL);
+ }
+ }
+
+ /*
+ * Set the relay agent info.
+ *
+ * Note that because agent info is appended without regard
+ * to the PRL in cons_options(), this will be sent as the
+ * last option in the packet whether it is listed on PRL or
+ * not.
+ */
+
+ if (lease->agent_options != NULL) {
+ int idx = agent_universe.index;
+ struct option_chain_head **tmp1 =
+ (struct option_chain_head **)
+ &(options->universes[idx]);
+ struct option_chain_head *tmp2 =
+ (struct option_chain_head *)
+ lease->agent_options;
+
+ option_chain_head_reference(tmp1, tmp2, MDL);
+ }
+
+ /*
+ * Set the client last transaction time.
+ * We check to make sure we have a timestamp. For
+ * lease files that were saved before running a
+ * timestamp-aware version of the server, this may
+ * not be set.
+ */
+
+ if (lease->cltt != MIN_TIME) {
+ if (cur_time > lease->cltt) {
+ client_last_transaction_time =
+ htonl(cur_time - lease->cltt);
+ } else {
+ client_last_transaction_time = htonl(0);
+ }
+ if (!add_option(options,
+ DHO_CLIENT_LAST_TRANSACTION_TIME,
+ &client_last_transaction_time,
+ sizeof(client_last_transaction_time))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ /*
+ * Set associated IPs, if requested and there are some.
+ */
+ if (want_associated_ip && (assoc_ip_cnt > 0)) {
+ if (!add_option(options,
+ DHO_ASSOCIATED_IP,
+ assoc_ips,
+ assoc_ip_cnt * sizeof(assoc_ips[0]))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Set the message type.
+ */
+
+ packet->raw->op = BOOTREPLY;
+
+ /*
+ * Set DHCP message type.
+ */
+ if (!add_option(options,
+ DHO_DHCP_MESSAGE_TYPE,
+ &dhcpMsgType,
+ sizeof(dhcpMsgType))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: error adding option, no reply sent", msgbuf);
+ return;
+ }
+
+ /*
+ * Log the message we've received.
+ */
+ log_info("%s", msgbuf);
+
+ /*
+ * Figure out which address to use to send from.
+ */
+ get_server_source_address(&siaddr, options, packet);
+
+ /*
+ * Set up the option buffer.
+ */
+
+ memset(&prl, 0, sizeof(prl));
+ oc = lookup_option(&dhcp_universe, options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+ if (oc != NULL) {
+ evaluate_option_cache(&prl,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ oc,
+ MDL);
+ }
+ if (prl.len > 0) {
+ prl_ptr = &prl;
+ } else {
+ prl_ptr = NULL;
+ }
+
+ packet->packet_length = cons_options(packet,
+ packet->raw,
+ lease,
+ NULL,
+ 0,
+ packet->options,
+ options,
+ &global_scope,
+ 0,
+ 0,
+ 0,
+ prl_ptr,
+ NULL);
+
+ data_string_forget(&prl, MDL); /* SK: safe, even if empty */
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+
+ to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ to.sin_len = sizeof(to);
+#endif
+ memset(to.sin_zero, 0, sizeof(to.sin_zero));
+
+ /*
+ * Leasequery packets are be sent to the gateway address.
+ */
+ to.sin_addr = packet->raw->giaddr;
+ if (packet->raw->giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
+ to.sin_port = local_port;
+ } else {
+ to.sin_port = remote_port; /* XXXSK: For debugging. */
+ }
+
+ /*
+ * The fallback_interface lets us send with a real IP
+ * address. The packet interface sends from all-zeros.
+ */
+ if (fallback_interface != NULL) {
+ interface = fallback_interface;
+ } else {
+ interface = packet->interface;
+ }
+
+ /*
+ * Report what we're sending.
+ */
+ log_info("%s to %s for %s (%d associated IPs)",
+ dhcp_msg_type_name,
+ inet_ntoa(to.sin_addr), dbg_info, assoc_ip_cnt);
+
+ send_packet(interface,
+ NULL,
+ packet->raw,
+ packet->packet_length,
+ siaddr,
+ &to,
+ NULL);
+}
+
+#ifdef DHCPv6
+
+/*
+ * TODO: RFC5007 query-by-clientid.
+ *
+ * TODO: RFC5007 look at the pools according to the link-address.
+ *
+ * TODO: get fixed leases too.
+ *
+ * TODO: RFC5007 ORO in query-options.
+ *
+ * TODO: RFC5007 lq-relay-data.
+ *
+ * TODO: RFC5007 lq-client-link.
+ *
+ * Note: the code is still nearly compliant and usable for the target
+ * case with these missing features!
+ */
+
+/*
+ * The structure to handle a leasequery.
+ */
+struct lq6_state {
+ struct packet *packet;
+ struct data_string client_id;
+ struct data_string server_id;
+ struct data_string lq_query;
+ uint8_t query_type;
+ struct in6_addr link_addr;
+ struct option_state *query_opts;
+
+ struct option_state *reply_opts;
+ unsigned cursor;
+ union reply_buffer {
+ unsigned char data[65536];
+ struct dhcpv6_packet reply;
+ } buf;
+};
+
+/*
+ * Options that we want to send.
+ */
+static const int required_opts_lq[] = {
+ D6O_CLIENTID,
+ D6O_SERVERID,
+ D6O_STATUS_CODE,
+ D6O_CLIENT_DATA,
+ D6O_LQ_RELAY_DATA,
+ D6O_LQ_CLIENT_LINK,
+ 0
+};
+static const int required_opt_CLIENT_DATA[] = {
+ D6O_CLIENTID,
+ D6O_IAADDR,
+ D6O_IAPREFIX,
+ D6O_CLT_TIME,
+ 0
+};
+
+/*
+ * Get the lq-query option from the packet.
+ */
+static isc_result_t
+get_lq_query(struct lq6_state *lq)
+{
+ struct data_string *lq_query = &lq->lq_query;
+ struct packet *packet = lq->packet;
+ struct option_cache *oc;
+
+ /*
+ * Verify our lq_query structure is empty.
+ */
+ if ((lq_query->data != NULL) || (lq_query->len != 0)) {
+ return DHCP_R_INVALIDARG;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_LQ_QUERY);
+ if (oc == NULL) {
+ return ISC_R_NOTFOUND;
+ }
+
+ if (!evaluate_option_cache(lq_query, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ return ISC_R_FAILURE;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+/*
+ * Message validation, RFC 5007 section 4.2.1:
+ * dhcpv6.c:valid_client_msg() - unicast + lq-query option.
+ */
+static int
+valid_query_msg(struct lq6_state *lq) {
+ struct packet *packet = lq->packet;
+ int ret_val = 0;
+ struct option_cache *oc;
+
+ /* INSIST((lq != NULL) || (packet != NULL)); */
+
+ switch (get_client_id(packet, &lq->client_id)) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NOTFOUND:
+ log_debug("Discarding %s from %s; "
+ "client identifier missing",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ default:
+ log_error("Error processing %s from %s; "
+ "unable to evaluate Client Identifier",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ }
+
+ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
+ if (oc != NULL) {
+ if (evaluate_option_cache(&lq->server_id, packet, NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_debug("Discarding %s from %s; "
+ "server identifier found "
+ "(CLIENTID %s, SERVERID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr),
+ print_hex_1(lq->client_id.len,
+ lq->client_id.data, 60),
+ print_hex_2(lq->server_id.len,
+ lq->server_id.data, 60));
+ } else {
+ log_debug("Discarding %s from %s; "
+ "server identifier found "
+ "(CLIENTID %s)",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ print_hex_1(lq->client_id.len,
+ lq->client_id.data, 60),
+ piaddr(packet->client_addr));
+ }
+ goto exit;
+ }
+
+ switch (get_lq_query(lq)) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NOTFOUND:
+ log_debug("Discarding %s from %s; lq-query missing",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ default:
+ log_error("Error processing %s from %s; "
+ "unable to evaluate LQ-Query",
+ dhcpv6_type_names[packet->dhcpv6_msg_type],
+ piaddr(packet->client_addr));
+ goto exit;
+ }
+
+ /* looks good */
+ ret_val = 1;
+
+exit:
+ if (!ret_val) {
+ if (lq->client_id.len > 0) {
+ data_string_forget(&lq->client_id, MDL);
+ }
+ if (lq->server_id.len > 0) {
+ data_string_forget(&lq->server_id, MDL);
+ }
+ if (lq->lq_query.len > 0) {
+ data_string_forget(&lq->lq_query, MDL);
+ }
+ }
+ return ret_val;
+}
+
+/*
+ * Set an error in a status-code option (from set_status_code).
+ */
+static int
+set_error(struct lq6_state *lq, u_int16_t code, const char *message) {
+ struct data_string d;
+ int ret_val;
+
+ memset(&d, 0, sizeof(d));
+ d.len = sizeof(code) + strlen(message);
+ if (!buffer_allocate(&d.buffer, d.len, MDL)) {
+ log_fatal("set_error: no memory for status code.");
+ }
+ d.data = d.buffer->data;
+ putUShort(d.buffer->data, code);
+ memcpy(d.buffer->data + sizeof(code), message, d.len - sizeof(code));
+ if (!save_option_buffer(&dhcpv6_universe, lq->reply_opts,
+ d.buffer, (unsigned char *)d.data, d.len,
+ D6O_STATUS_CODE, 0)) {
+ log_error("set_error: error saving status code.");
+ ret_val = 0;
+ } else {
+ ret_val = 1;
+ }
+ data_string_forget(&d, MDL);
+ return ret_val;
+}
+
+/*
+ * Process a by-address lease query.
+ */
+static int
+process_lq_by_address(struct lq6_state *lq) {
+ struct packet *packet = lq->packet;
+ struct option_cache *oc;
+ struct ipv6_pool *pool = NULL;
+ struct data_string data;
+ struct in6_addr addr;
+ struct iasubopt *iaaddr = NULL;
+ struct option_state *opt_state = NULL;
+ u_int32_t lifetime;
+ unsigned opt_cursor;
+ int ret_val = 0;
+
+ /*
+ * Get the IAADDR.
+ */
+ oc = lookup_option(&dhcpv6_universe, lq->query_opts, D6O_IAADDR);
+ if (oc == NULL) {
+ if (!set_error(lq, STATUS_MalformedQuery,
+ "No OPTION_IAADDR.")) {
+ log_error("process_lq_by_address: unable "
+ "to set MalformedQuery status code.");
+ return 0;
+ }
+ return 1;
+ }
+ memset(&data, 0, sizeof(data));
+ if (!evaluate_option_cache(&data, packet,
+ NULL, NULL,
+ lq->query_opts, NULL,
+ &global_scope, oc, MDL) ||
+ (data.len < IAADDR_OFFSET)) {
+ log_error("process_lq_by_address: error evaluating IAADDR.");
+ goto exit;
+ }
+ memcpy(&addr, data.data, sizeof(addr));
+ data_string_forget(&data, MDL);
+
+ /*
+ * Find the lease.
+ * Note the RFC 5007 says to use the link-address to find the link
+ * or the ia-aadr when it is :: but in any case the ia-addr has
+ * to be on the link, so we ignore the link-address here.
+ */
+ if (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_SUCCESS) {
+ if (!set_error(lq, STATUS_NotConfigured,
+ "Address not in a pool.")) {
+ log_error("process_lq_by_address: unable "
+ "to set NotConfigured status code.");
+ goto exit;
+ }
+ ret_val = 1;
+ goto exit;
+ }
+ if (iasubopt_hash_lookup(&iaaddr, pool->leases, &addr,
+ sizeof(addr), MDL) == 0) {
+ ret_val = 1;
+ goto exit;
+ }
+ if ((iaaddr == NULL) || (iaaddr->state != FTS_ACTIVE) ||
+ (iaaddr->ia == NULL) || (iaaddr->ia->iaid_duid.len <= 4)) {
+ ret_val = 1;
+ goto exit;
+ }
+
+ /*
+ * Build the client-data option (with client-id, ia-addr and clt-time).
+ */
+ if (!option_state_allocate(&opt_state, MDL)) {
+ log_error("process_lq_by_address: "
+ "no memory for option state.");
+ goto exit;
+ }
+
+ data_string_copy(&data, &iaaddr->ia->iaid_duid, MDL);
+ data.data += 4;
+ data.len -= 4;
+ if (!save_option_buffer(&dhcpv6_universe, opt_state,
+ NULL, (unsigned char *)data.data, data.len,
+ D6O_CLIENTID, 0)) {
+ log_error("process_lq_by_address: error saving client ID.");
+ goto exit;
+ }
+ data_string_forget(&data, MDL);
+
+ data.len = IAADDR_OFFSET;
+ if (!buffer_allocate(&data.buffer, data.len, MDL)) {
+ log_error("process_lq_by_address: no memory for ia-addr.");
+ goto exit;
+ }
+ data.data = data.buffer->data;
+ memcpy(data.buffer->data, &iaaddr->addr, 16);
+ lifetime = iaaddr->prefer;
+ putULong(data.buffer->data + 16, lifetime);
+ lifetime = iaaddr->valid;
+ putULong(data.buffer->data + 20, lifetime);
+ if (!save_option_buffer(&dhcpv6_universe, opt_state,
+ NULL, (unsigned char *)data.data, data.len,
+ D6O_IAADDR, 0)) {
+ log_error("process_lq_by_address: error saving ia-addr.");
+ goto exit;
+ }
+ data_string_forget(&data, MDL);
+
+ lifetime = htonl(iaaddr->ia->cltt);
+ if (!save_option_buffer(&dhcpv6_universe, opt_state,
+ NULL, (unsigned char *)&lifetime, 4,
+ D6O_CLT_TIME, 0)) {
+ log_error("process_lq_by_address: error saving clt time.");
+ goto exit;
+ }
+
+ /*
+ * Store the client-data option.
+ */
+ opt_cursor = lq->cursor;
+ putUShort(lq->buf.data + lq->cursor, (unsigned)D6O_CLIENT_DATA);
+ lq->cursor += 2;
+ /* Skip option length. */
+ lq->cursor += 2;
+
+ lq->cursor += store_options6((char *)lq->buf.data + lq->cursor,
+ sizeof(lq->buf) - lq->cursor,
+ opt_state, lq->packet,
+ required_opt_CLIENT_DATA, NULL);
+ /* Reset the length. */
+ putUShort(lq->buf.data + opt_cursor + 2,
+ lq->cursor - (opt_cursor + 4));
+
+ /* Done. */
+ ret_val = 1;
+
+ exit:
+ if (data.data != NULL)
+ data_string_forget(&data, MDL);
+ if (pool != NULL)
+ ipv6_pool_dereference(&pool, MDL);
+ if (iaaddr != NULL)
+ iasubopt_dereference(&iaaddr, MDL);
+ if (opt_state != NULL)
+ option_state_dereference(&opt_state, MDL);
+ return ret_val;
+}
+
+
+/*
+ * Process a lease query.
+ */
+void
+dhcpv6_leasequery(struct data_string *reply_ret, struct packet *packet) {
+ static struct lq6_state lq;
+ struct option_cache *oc;
+ int allow_lq;
+
+ /*
+ * Initialize the lease query state.
+ */
+ lq.packet = NULL;
+ memset(&lq.client_id, 0, sizeof(lq.client_id));
+ memset(&lq.server_id, 0, sizeof(lq.server_id));
+ memset(&lq.lq_query, 0, sizeof(lq.lq_query));
+ lq.query_opts = NULL;
+ lq.reply_opts = NULL;
+ packet_reference(&lq.packet, packet, MDL);
+
+ /*
+ * Validate our input.
+ */
+ if (!valid_query_msg(&lq)) {
+ goto exit;
+ }
+
+ /*
+ * Prepare our reply.
+ */
+ if (!option_state_allocate(&lq.reply_opts, MDL)) {
+ log_error("dhcpv6_leasequery: no memory for option state.");
+ goto exit;
+ }
+ execute_statements_in_scope(NULL, lq.packet, NULL, NULL,
+ lq.packet->options, lq.reply_opts,
+ &global_scope, root_group, NULL);
+
+ lq.buf.reply.msg_type = DHCPV6_LEASEQUERY_REPLY;
+
+ memcpy(lq.buf.reply.transaction_id,
+ lq.packet->dhcpv6_transaction_id,
+ sizeof(lq.buf.reply.transaction_id));
+
+ /*
+ * Because LEASEQUERY has some privacy concerns, default to deny.
+ */
+ allow_lq = 0;
+
+ /*
+ * See if we are authorized to do LEASEQUERY.
+ */
+ oc = lookup_option(&server_universe, lq.reply_opts, SV_LEASEQUERY);
+ if (oc != NULL) {
+ allow_lq = evaluate_boolean_option_cache(NULL,
+ lq.packet,
+ NULL, NULL,
+ lq.packet->options,
+ lq.reply_opts,
+ &global_scope,
+ oc, MDL);
+ }
+
+ if (!allow_lq) {
+ log_info("dhcpv6_leasequery: not allowed, query ignored.");
+ goto exit;
+ }
+
+ /*
+ * Same than transmission of REPLY message in RFC 3315:
+ * server-id
+ * client-id
+ */
+
+ oc = lookup_option(&dhcpv6_universe, lq.reply_opts, D6O_SERVERID);
+ if (oc == NULL) {
+ /* If not already in options, get from query then global. */
+ if (lq.server_id.data == NULL)
+ copy_server_duid(&lq.server_id, MDL);
+ if (!save_option_buffer(&dhcpv6_universe,
+ lq.reply_opts,
+ NULL,
+ (unsigned char *)lq.server_id.data,
+ lq.server_id.len,
+ D6O_SERVERID,
+ 0)) {
+ log_error("dhcpv6_leasequery: "
+ "error saving server identifier.");
+ goto exit;
+ }
+ }
+
+ if (!save_option_buffer(&dhcpv6_universe,
+ lq.reply_opts,
+ lq.client_id.buffer,
+ (unsigned char *)lq.client_id.data,
+ lq.client_id.len,
+ D6O_CLIENTID,
+ 0)) {
+ log_error("dhcpv6_leasequery: "
+ "error saving client identifier.");
+ goto exit;
+ }
+
+ lq.cursor = 4;
+
+ /*
+ * Decode the lq-query option.
+ */
+
+ if (lq.lq_query.len <= LQ_QUERY_OFFSET) {
+ if (!set_error(&lq, STATUS_MalformedQuery,
+ "OPTION_LQ_QUERY too short.")) {
+ log_error("dhcpv6_leasequery: unable "
+ "to set MalformedQuery status code.");
+ goto exit;
+ }
+ goto done;
+ }
+
+ lq.query_type = lq.lq_query.data [0];
+ memcpy(&lq.link_addr, lq.lq_query.data + 1, sizeof(lq.link_addr));
+ switch (lq.query_type) {
+ case LQ6QT_BY_ADDRESS:
+ break;
+ case LQ6QT_BY_CLIENTID:
+ if (!set_error(&lq, STATUS_UnknownQueryType,
+ "QUERY_BY_CLIENTID not supported.")) {
+ log_error("dhcpv6_leasequery: unable to "
+ "set UnknownQueryType status code.");
+ goto exit;
+ }
+ goto done;
+ default:
+ if (!set_error(&lq, STATUS_UnknownQueryType,
+ "Unknown query-type.")) {
+ log_error("dhcpv6_leasequery: unable to "
+ "set UnknownQueryType status code.");
+ goto exit;
+ }
+ goto done;
+ }
+
+ if (!option_state_allocate(&lq.query_opts, MDL)) {
+ log_error("dhcpv6_leasequery: no memory for option state.");
+ goto exit;
+ }
+ if (!parse_option_buffer(lq.query_opts,
+ lq.lq_query.data + LQ_QUERY_OFFSET,
+ lq.lq_query.len - LQ_QUERY_OFFSET,
+ &dhcpv6_universe)) {
+ log_error("dhcpv6_leasequery: error parsing query-options.");
+ if (!set_error(&lq, STATUS_MalformedQuery,
+ "Bad query-options.")) {
+ log_error("dhcpv6_leasequery: unable "
+ "to set MalformedQuery status code.");
+ goto exit;
+ }
+ goto done;
+ }
+
+ /* Do it. */
+ if (!process_lq_by_address(&lq))
+ goto exit;
+
+ done:
+ /* Store the options. */
+ lq.cursor += store_options6((char *)lq.buf.data + lq.cursor,
+ sizeof(lq.buf) - lq.cursor,
+ lq.reply_opts,
+ lq.packet,
+ required_opts_lq,
+ NULL);
+
+ /* Return our reply to the caller. */
+ reply_ret->len = lq.cursor;
+ reply_ret->buffer = NULL;
+ if (!buffer_allocate(&reply_ret->buffer, lq.cursor, MDL)) {
+ log_fatal("dhcpv6_leasequery: no memory to store Reply.");
+ }
+ memcpy(reply_ret->buffer->data, lq.buf.data, lq.cursor);
+ reply_ret->data = reply_ret->buffer->data;
+
+ exit:
+ /* Cleanup. */
+ if (lq.packet != NULL)
+ packet_dereference(&lq.packet, MDL);
+ if (lq.client_id.data != NULL)
+ data_string_forget(&lq.client_id, MDL);
+ if (lq.server_id.data != NULL)
+ data_string_forget(&lq.server_id, MDL);
+ if (lq.lq_query.data != NULL)
+ data_string_forget(&lq.lq_query, MDL);
+ if (lq.query_opts != NULL)
+ option_state_dereference(&lq.query_opts, MDL);
+ if (lq.reply_opts != NULL)
+ option_state_dereference(&lq.reply_opts, MDL);
+}
+
+#endif /* DHCPv6 */