1/*	$NetBSD: dhcpleasequery.c,v 1.3 2020/08/03 21:10:57 christos Exp $	*/
2
3/*
4 * Copyright (C) 2006-2017 by Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
11 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
13 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
15 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16 * PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/cdefs.h>
20__RCSID("$NetBSD: dhcpleasequery.c,v 1.3 2020/08/03 21:10:57 christos Exp $");
21
22
23#include "dhcpd.h"
24
25/*
26 * TODO: RFC4388 specifies that the server SHOULD return the same
27 *       options it would for a DHCREQUEST message, if no Parameter
28 *       Request List option (option 55) is passed. We do not do that.
29 *
30 * TODO: RFC4388 specifies the creation of a "non-sensitive options"
31 *       configuration list, and that these SHOULD be returned. We
32 *       have no such list.
33 *
34 * TODO: RFC4388 says the server SHOULD use RFC3118, "Authentication
35 *       for DHCP Messages".
36 *
37 * TODO: RFC4388 specifies that you SHOULD insure that you cannot be
38 *       DoS'ed by DHCPLEASEQUERY message.
39 */
40
41/*
42 * If you query by hardware address or by client ID, then you may have
43 * more than one IP address for your query argument. We need to do two
44 * things:
45 *
46 *   1. Find the most recent lease.
47 *   2. Find all additional IP addresses for the query argument.
48 *
49 * We do this by looking through all of the leases associated with a
50 * given hardware address or client ID. We use the cltt (client last
51 * transaction time) of the lease, which only has a resolution of one
52 * second, so we might not actually give the very latest IP.
53 */
54
55static struct lease*
56next_hw(const struct lease *lease) {
57	/* INSIST(lease != NULL); */
58	return lease->n_hw;
59}
60
61static struct lease*
62next_uid(const struct lease *lease) {
63	/* INSIST(lease != NULL); */
64	return lease->n_uid;
65}
66
67static void
68get_newest_lease(struct lease **retval,
69		 struct lease *lease,
70		 struct lease *(*next)(const struct lease *)) {
71
72	struct lease *p;
73	struct lease *newest;
74
75	/* INSIST(newest != NULL); */
76	/* INSIST(next != NULL); */
77
78	*retval = NULL;
79
80	if (lease == NULL) {
81		return;
82	}
83
84	newest = lease;
85	for (p=next(lease); p != NULL; p=next(p)) {
86		if (newest->binding_state == FTS_ACTIVE) {
87			if ((p->binding_state == FTS_ACTIVE) &&
88		    	(p->cltt > newest->cltt)) {
89				newest = p;
90			}
91		} else {
92			if (p->ends > newest->ends) {
93				newest = p;
94			}
95		}
96	}
97
98	lease_reference(retval, newest, MDL);
99}
100
101static int
102get_associated_ips(const struct lease *lease,
103		   struct lease *(*next)(const struct lease *),
104		   const struct lease *newest,
105		   u_int32_t *associated_ips,
106		   unsigned int associated_ips_size) {
107
108	const struct lease *p;
109	int cnt;
110
111	/* INSIST(next != NULL); */
112	/* INSIST(associated_ips != NULL); */
113
114	if (lease == NULL) {
115		return 0;
116	}
117
118	cnt = 0;
119	for (p=lease; p != NULL; p=next(p)) {
120		if ((p->binding_state == FTS_ACTIVE) && (p != newest)) {
121			if (cnt < associated_ips_size) {
122				memcpy(&associated_ips[cnt],
123				       p->ip_addr.iabuf,
124				       sizeof(associated_ips[cnt]));
125			}
126			cnt++;
127		}
128	}
129	return cnt;
130}
131
132
133void
134dhcpleasequery(struct packet *packet, int ms_nulltp) {
135	char msgbuf[256];
136	char dbg_info[128];
137	struct iaddr cip;
138	struct iaddr gip;
139	struct data_string uid;
140	struct hardware h;
141	struct lease *tmp_lease;
142	struct lease *lease;
143	int want_associated_ip;
144	int assoc_ip_cnt;
145	u_int32_t assoc_ips[40];  /* XXXSK: arbitrary maximum number of IPs */
146	const int nassoc_ips = sizeof(assoc_ips) / sizeof(assoc_ips[0]);
147
148	unsigned char dhcpMsgType;
149	const char *dhcp_msg_type_name;
150	struct subnet *subnet;
151	struct group *relay_group;
152	struct option_state *options;
153	struct option_cache *oc;
154	int allow_leasequery;
155	int ignorep;
156	u_int32_t lease_duration;
157	u_int32_t time_renewal;
158	u_int32_t time_rebinding;
159	u_int32_t time_expiry;
160	u_int32_t client_last_transaction_time;
161#if defined(RELAY_PORT)
162	u_int16_t relay_port = 0;
163#endif
164	struct sockaddr_in to;
165	struct in_addr siaddr;
166	struct data_string prl;
167	struct data_string *prl_ptr;
168
169	int i;
170	struct interface_info *interface;
171
172	/* INSIST(packet != NULL); */
173
174	/*
175	 * Prepare log information.
176	 */
177	snprintf(msgbuf, sizeof(msgbuf),
178		"DHCPLEASEQUERY from %s", inet_ntoa(packet->raw->giaddr));
179
180	/*
181	 * We can't reply if there is no giaddr field.
182	 */
183	/*
184	 * Note: this makes DHCPv4-over-DHCPv6 always fail but it should not
185	 * really be a problem because it is not a specified use case
186	 * (or even one that makes sense).
187	 */
188	if (!packet->raw->giaddr.s_addr) {
189		log_info("%s: missing giaddr, ciaddr is %s, no reply sent",
190			 msgbuf, inet_ntoa(packet->raw->ciaddr));
191		return;
192	}
193
194	/*
195	 * Initially we use the 'giaddr' subnet options scope to determine if
196	 * the giaddr-identified relay agent is permitted to perform a
197	 * leasequery.  The subnet is not required, and may be omitted, in
198	 * which case we are essentially interrogating the root options class
199	 * to find a globally permit.
200	 */
201	gip.len = sizeof(packet->raw->giaddr);
202	memcpy(gip.iabuf, &packet->raw->giaddr, sizeof(packet->raw->giaddr));
203
204	subnet = NULL;
205	find_subnet(&subnet, gip, MDL);
206	if (subnet != NULL)
207		relay_group = subnet->group;
208	else
209		relay_group = root_group;
210
211	subnet_dereference(&subnet, MDL);
212
213	options = NULL;
214	if (!option_state_allocate(&options, MDL)) {
215		log_error("No memory for option state.");
216		log_info("%s: out of memory, no reply sent", msgbuf);
217		return;
218	}
219
220	execute_statements_in_scope(NULL, packet, NULL, NULL, packet->options,
221				    options, &global_scope, relay_group,
222				    NULL, NULL);
223
224	for (i=packet->class_count-1; i>=0; i--) {
225		execute_statements_in_scope(NULL, packet, NULL, NULL,
226					    packet->options, options,
227					    &global_scope,
228					    packet->classes[i]->group,
229					    relay_group, NULL);
230	}
231
232	/*
233	 * Because LEASEQUERY has some privacy concerns, default to deny.
234	 */
235	allow_leasequery = 0;
236
237	/*
238	 * See if we are authorized to do LEASEQUERY.
239	 */
240	oc = lookup_option(&server_universe, options, SV_LEASEQUERY);
241	if (oc != NULL) {
242		allow_leasequery = evaluate_boolean_option_cache(&ignorep,
243					 packet, NULL, NULL, packet->options,
244					 options, &global_scope, oc, MDL);
245	}
246
247	if (!allow_leasequery) {
248		log_info("%s: LEASEQUERY not allowed, query ignored", msgbuf);
249		option_state_dereference(&options, MDL);
250		return;
251	}
252
253
254	/*
255	 * Copy out the client IP address.
256	 */
257	cip.len = sizeof(packet->raw->ciaddr);
258	memcpy(cip.iabuf, &packet->raw->ciaddr, sizeof(packet->raw->ciaddr));
259
260	/*
261	 * If the client IP address is valid (not all zero), then we
262	 * are looking for information about that IP address.
263	 */
264	assoc_ip_cnt = 0;
265	lease = tmp_lease = NULL;
266	if (memcmp(cip.iabuf, "\0\0\0", 4)) {
267
268		want_associated_ip = 0;
269
270		snprintf(dbg_info, sizeof(dbg_info), "IP %s", piaddr(cip));
271		find_lease_by_ip_addr(&lease, cip, MDL);
272
273
274	} else {
275
276		want_associated_ip = 1;
277
278		/*
279		 * If the client IP address is all zero, then we will
280		 * either look up by the client identifier (if we have
281		 * one), or by the MAC address.
282		 */
283
284		memset(&uid, 0, sizeof(uid));
285		if (get_option(&uid,
286			       &dhcp_universe,
287			       packet,
288			       NULL,
289			       NULL,
290			       packet->options,
291			       NULL,
292			       packet->options,
293			       &global_scope,
294			       DHO_DHCP_CLIENT_IDENTIFIER,
295			       MDL)) {
296
297			snprintf(dbg_info,
298				 sizeof(dbg_info),
299				 "client-id %s",
300				 print_hex_1(uid.len, uid.data, 60));
301
302			find_lease_by_uid(&tmp_lease, uid.data, uid.len, MDL);
303			data_string_forget(&uid, MDL);
304			get_newest_lease(&lease, tmp_lease, next_uid);
305			assoc_ip_cnt = get_associated_ips(tmp_lease,
306							  next_uid,
307							  lease,
308							  assoc_ips,
309							  nassoc_ips);
310
311		} else {
312
313			if (packet->raw->hlen+1 > sizeof(h.hbuf)) {
314				log_info("%s: hardware length too long, "
315					 "no reply sent", msgbuf);
316				option_state_dereference(&options, MDL);
317				return;
318			}
319
320			h.hlen = packet->raw->hlen + 1;
321			h.hbuf[0] = packet->raw->htype;
322			memcpy(&h.hbuf[1],
323			       packet->raw->chaddr,
324			       packet->raw->hlen);
325
326			snprintf(dbg_info,
327				 sizeof(dbg_info),
328				 "MAC address %s",
329				 print_hw_addr(h.hbuf[0],
330					       h.hlen - 1,
331					       &h.hbuf[1]));
332
333			find_lease_by_hw_addr(&tmp_lease, h.hbuf, h.hlen, MDL);
334			get_newest_lease(&lease, tmp_lease, next_hw);
335			assoc_ip_cnt = get_associated_ips(tmp_lease,
336							  next_hw,
337							  lease,
338							  assoc_ips,
339							  nassoc_ips);
340
341		}
342
343		lease_dereference(&tmp_lease, MDL);
344
345		if (lease != NULL) {
346			memcpy(&packet->raw->ciaddr,
347			       lease->ip_addr.iabuf,
348			       sizeof(packet->raw->ciaddr));
349		}
350
351		/*
352		 * Log if we have too many IP addresses associated
353		 * with this client.
354		 */
355		if (want_associated_ip && (assoc_ip_cnt > nassoc_ips)) {
356			log_info("%d IP addresses associated with %s, "
357				 "only %d sent in reply.",
358				 assoc_ip_cnt, dbg_info, nassoc_ips);
359		}
360	}
361
362	/*
363	 * We now know the query target too, so can report this in
364	 * our log message.
365	 */
366	snprintf(msgbuf, sizeof(msgbuf),
367		"DHCPLEASEQUERY from %s for %s",
368		inet_ntoa(packet->raw->giaddr), dbg_info);
369
370	/*
371	 * Figure our our return type.
372	 */
373	if (lease == NULL) {
374		dhcpMsgType = DHCPLEASEUNKNOWN;
375		dhcp_msg_type_name = "DHCPLEASEUNKNOWN";
376	} else {
377		if (lease->binding_state == FTS_ACTIVE) {
378			dhcpMsgType = DHCPLEASEACTIVE;
379			dhcp_msg_type_name = "DHCPLEASEACTIVE";
380		} else {
381			dhcpMsgType = DHCPLEASEUNASSIGNED;
382			dhcp_msg_type_name = "DHCPLEASEUNASSIGNED";
383		}
384	}
385
386	/*
387	 * Set options that only make sense if we have an active lease.
388	 */
389
390	if (dhcpMsgType == DHCPLEASEACTIVE)
391	{
392		/*
393		 * RFC 4388 uses the PRL to request options for the agent to
394		 * receive that are "about" the client.  It is confusing
395		 * because in some cases it wants to know what was sent to
396		 * the client (lease times, adjusted), and in others it wants
397		 * to know information the client sent.  You're supposed to
398		 * know this on a case-by-case basis.
399		 *
400		 * "Name servers", "domain name", and the like from the relay
401		 * agent's scope seems less than useful.  Our options are to
402		 * restart the option cache from the lease's best point of view
403		 * (execute statements from the lease pool's group), or to
404		 * simply restart the option cache from empty.
405		 *
406		 * I think restarting the option cache from empty best
407		 * approaches RFC 4388's intent; specific options are included.
408		 */
409		option_state_dereference(&options, MDL);
410
411		if (!option_state_allocate(&options, MDL)) {
412			log_error("%s: out of memory, no reply sent", msgbuf);
413			lease_dereference(&lease, MDL);
414			return;
415		}
416
417		/*
418		 * Set the hardware address fields.
419		 */
420
421		packet->raw->hlen = lease->hardware_addr.hlen - 1;
422		packet->raw->htype = lease->hardware_addr.hbuf[0];
423		memcpy(packet->raw->chaddr,
424		       &lease->hardware_addr.hbuf[1],
425		       sizeof(packet->raw->chaddr));
426
427		/*
428		 * Set client identifier option.
429		 */
430		if (lease->uid_len > 0) {
431			if (!add_option(options,
432					DHO_DHCP_CLIENT_IDENTIFIER,
433					lease->uid,
434					lease->uid_len)) {
435				option_state_dereference(&options, MDL);
436				lease_dereference(&lease, MDL);
437				log_info("%s: out of memory, no reply sent",
438					 msgbuf);
439				return;
440			}
441		}
442
443
444		/*
445		 * Calculate T1 and T2, the times when the client
446		 * tries to extend its lease on its networking
447		 * address.
448		 * These seem to be hard-coded in ISC DHCP, to 0.5 and
449		 * 0.875 of the lease time.
450		 */
451
452		lease_duration = lease->ends - lease->starts;
453		time_renewal = lease->starts +
454			(lease_duration / 2);
455		time_rebinding = lease->starts +
456			(lease_duration / 2) +
457			(lease_duration / 4) +
458			(lease_duration / 8);
459
460		if (time_renewal > cur_time) {
461			time_renewal = htonl(time_renewal - cur_time);
462
463			if (!add_option(options,
464					DHO_DHCP_RENEWAL_TIME,
465					&time_renewal,
466					sizeof(time_renewal))) {
467				option_state_dereference(&options, MDL);
468				lease_dereference(&lease, MDL);
469				log_info("%s: out of memory, no reply sent",
470					 msgbuf);
471				return;
472			}
473		}
474
475		if (time_rebinding > cur_time) {
476			time_rebinding = htonl(time_rebinding - cur_time);
477
478			if (!add_option(options,
479					DHO_DHCP_REBINDING_TIME,
480					&time_rebinding,
481					sizeof(time_rebinding))) {
482				option_state_dereference(&options, MDL);
483				lease_dereference(&lease, MDL);
484				log_info("%s: out of memory, no reply sent",
485					 msgbuf);
486				return;
487			}
488		}
489
490		if (lease->ends > cur_time) {
491			time_expiry = htonl(lease->ends - cur_time);
492
493			if (!add_option(options,
494					DHO_DHCP_LEASE_TIME,
495					&time_expiry,
496					sizeof(time_expiry))) {
497				option_state_dereference(&options, MDL);
498				lease_dereference(&lease, MDL);
499				log_info("%s: out of memory, no reply sent",
500					 msgbuf);
501				return;
502			}
503		}
504
505		/* Supply the Vendor-Class-Identifier. */
506		if (lease->scope != NULL) {
507			struct data_string vendor_class;
508
509			memset(&vendor_class, 0, sizeof(vendor_class));
510
511			if (find_bound_string(&vendor_class, lease->scope,
512					      "vendor-class-identifier")) {
513				if (!add_option(options,
514						DHO_VENDOR_CLASS_IDENTIFIER,
515						(void *)vendor_class.data,
516						vendor_class.len)) {
517					option_state_dereference(&options,
518								 MDL);
519					lease_dereference(&lease, MDL);
520					log_error("%s: error adding vendor "
521						  "class identifier, no reply "
522						  "sent", msgbuf);
523					data_string_forget(&vendor_class, MDL);
524					return;
525				}
526				data_string_forget(&vendor_class, MDL);
527			}
528		}
529
530		/*
531		 * Set the relay agent info.
532		 *
533		 * Note that because agent info is appended without regard
534		 * to the PRL in cons_options(), this will be sent as the
535		 * last option in the packet whether it is listed on PRL or
536		 * not.
537		 */
538
539		if (lease->agent_options != NULL) {
540			int idx = agent_universe.index;
541			struct option_chain_head **tmp1 =
542				(struct option_chain_head **)
543				&(options->universes[idx]);
544				struct option_chain_head *tmp2 =
545				(struct option_chain_head *)
546				lease->agent_options;
547
548			option_chain_head_reference(tmp1, tmp2, MDL);
549		}
550
551		/*
552	 	 * Set the client last transaction time.
553		 * We check to make sure we have a timestamp. For
554		 * lease files that were saved before running a
555		 * timestamp-aware version of the server, this may
556		 * not be set.
557	 	 */
558
559		if (lease->cltt != MIN_TIME) {
560			if (cur_time > lease->cltt) {
561				client_last_transaction_time =
562					htonl(cur_time - lease->cltt);
563			} else {
564				client_last_transaction_time = htonl(0);
565			}
566			if (!add_option(options,
567					DHO_CLIENT_LAST_TRANSACTION_TIME,
568					&client_last_transaction_time,
569		     			sizeof(client_last_transaction_time))) {
570				option_state_dereference(&options, MDL);
571				lease_dereference(&lease, MDL);
572				log_info("%s: out of memory, no reply sent",
573					 msgbuf);
574				return;
575			}
576		}
577
578		/*
579	 	 * Set associated IPs, if requested and there are some.
580	 	 */
581		if (want_associated_ip && (assoc_ip_cnt > 0)) {
582			if (!add_option(options,
583					DHO_ASSOCIATED_IP,
584					assoc_ips,
585					assoc_ip_cnt * sizeof(assoc_ips[0]))) {
586				option_state_dereference(&options, MDL);
587				lease_dereference(&lease, MDL);
588				log_info("%s: out of memory, no reply sent",
589					 msgbuf);
590				return;
591			}
592		}
593	}
594
595	/*
596	 * Set the message type.
597	 */
598
599	packet->raw->op = BOOTREPLY;
600
601	/*
602	 * Set DHCP message type.
603	 */
604	if (!add_option(options,
605		        DHO_DHCP_MESSAGE_TYPE,
606		        &dhcpMsgType,
607			sizeof(dhcpMsgType))) {
608		option_state_dereference(&options, MDL);
609		lease_dereference(&lease, MDL);
610		log_info("%s: error adding option, no reply sent", msgbuf);
611		return;
612	}
613
614	/*
615	 * Log the message we've received.
616	 */
617	log_info("%s", msgbuf);
618
619	/*
620	 * Figure out which address to use to send from.
621	 */
622	get_server_source_address(&siaddr, options, options, packet);
623
624	/*
625	 * Set up the option buffer.
626	 */
627
628	memset(&prl, 0, sizeof(prl));
629	oc = lookup_option(&dhcp_universe, options,
630			   DHO_DHCP_PARAMETER_REQUEST_LIST);
631	if (oc != NULL) {
632		evaluate_option_cache(&prl,
633				      packet,
634				      NULL,
635				      NULL,
636				      packet->options,
637				      options,
638				      &global_scope,
639				      oc,
640				      MDL);
641	}
642	if (prl.len > 0) {
643		prl_ptr = &prl;
644	} else {
645		prl_ptr = NULL;
646	}
647
648	packet->packet_length = cons_options(packet,
649					     packet->raw,
650					     lease,
651					     NULL,
652					     0,
653					     packet->options,
654					     options,
655					     &global_scope,
656					     0,
657					     0,
658					     0,
659					     prl_ptr,
660					     NULL);
661
662	data_string_forget(&prl, MDL);	/* SK: safe, even if empty */
663	option_state_dereference(&options, MDL);
664	lease_dereference(&lease, MDL);
665
666	to.sin_family = AF_INET;
667#ifdef HAVE_SA_LEN
668	to.sin_len = sizeof(to);
669#endif
670	memset(to.sin_zero, 0, sizeof(to.sin_zero));
671
672#if defined(RELAY_PORT)
673	relay_port = dhcp_check_relayport(packet);
674#endif
675
676	/*
677	 * Leasequery packets are be sent to the gateway address.
678	 */
679	to.sin_addr = packet->raw->giaddr;
680	if (packet->raw->giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
681#if defined(RELAY_PORT)
682		to.sin_port = relay_port ? relay_port : local_port;
683#else
684		to.sin_port = local_port;
685#endif
686	} else {
687		to.sin_port = remote_port; /* XXXSK: For debugging. */
688	}
689
690	/*
691	 * The fallback_interface lets us send with a real IP
692	 * address. The packet interface sends from all-zeros.
693	 */
694	if (fallback_interface != NULL) {
695		interface = fallback_interface;
696	} else {
697		interface = packet->interface;
698	}
699
700	/*
701	 * Report what we're sending.
702	 */
703	log_info("%s to %s for %s (%d associated IPs)",
704		dhcp_msg_type_name,
705		inet_ntoa(to.sin_addr), dbg_info, assoc_ip_cnt);
706
707	send_packet(interface,
708		    NULL,
709		    packet->raw,
710		    packet->packet_length,
711		    siaddr,
712		    &to,
713		    NULL);
714}
715
716#ifdef DHCPv6
717
718/*
719 * TODO: RFC5007 query-by-clientid.
720 *
721 * TODO: RFC5007 look at the pools according to the link-address.
722 *
723 * TODO: get fixed leases too.
724 *
725 * TODO: RFC5007 ORO in query-options.
726 *
727 * TODO: RFC5007 lq-relay-data.
728 *
729 * TODO: RFC5007 lq-client-link.
730 *
731 * Note: the code is still nearly compliant and usable for the target
732 * case with these missing features!
733 */
734
735/*
736 * The structure to handle a leasequery.
737 */
738struct lq6_state {
739	struct packet *packet;
740	struct data_string client_id;
741	struct data_string server_id;
742	struct data_string lq_query;
743	uint8_t query_type;
744	struct in6_addr link_addr;
745	struct option_state *query_opts;
746
747	struct option_state *reply_opts;
748	unsigned cursor;
749	union reply_buffer {
750		unsigned char data[65536];
751		struct dhcpv6_packet reply;
752	} buf;
753};
754
755/*
756 * Options that we want to send.
757 */
758static const int required_opts_lq[] = {
759	D6O_CLIENTID,
760	D6O_SERVERID,
761	D6O_STATUS_CODE,
762	D6O_CLIENT_DATA,
763	D6O_LQ_RELAY_DATA,
764	D6O_LQ_CLIENT_LINK,
765	0
766};
767static const int required_opt_CLIENT_DATA[] = {
768	D6O_CLIENTID,
769	D6O_IAADDR,
770	D6O_IAPREFIX,
771	D6O_CLT_TIME,
772	0
773};
774
775/*
776 * Get the lq-query option from the packet.
777 */
778static isc_result_t
779get_lq_query(struct lq6_state *lq)
780{
781	struct data_string *lq_query = &lq->lq_query;
782	struct packet *packet = lq->packet;
783	struct option_cache *oc;
784
785	/*
786	 * Verify our lq_query structure is empty.
787	 */
788	if ((lq_query->data != NULL) || (lq_query->len != 0)) {
789		return DHCP_R_INVALIDARG;
790	}
791
792	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_LQ_QUERY);
793	if (oc == NULL) {
794		return ISC_R_NOTFOUND;
795	}
796
797	if (!evaluate_option_cache(lq_query, packet, NULL, NULL,
798				   packet->options, NULL,
799				   &global_scope, oc, MDL)) {
800		return ISC_R_FAILURE;
801	}
802
803	return ISC_R_SUCCESS;
804}
805
806/*
807 * Message validation, RFC 5007 section 4.2.1:
808 *  dhcpv6.c:valid_client_msg() - unicast + lq-query option.
809 */
810static int
811valid_query_msg(struct lq6_state *lq) {
812	struct packet *packet = lq->packet;
813	int ret_val = 0;
814	struct option_cache *oc;
815
816	/* INSIST((lq != NULL) || (packet != NULL)); */
817
818	switch (get_client_id(packet, &lq->client_id)) {
819		case ISC_R_SUCCESS:
820			break;
821		case ISC_R_NOTFOUND:
822			log_debug("Discarding %s from %s; "
823				  "client identifier missing",
824				  dhcpv6_type_names[packet->dhcpv6_msg_type],
825				  piaddr(packet->client_addr));
826			goto exit;
827		default:
828			log_error("Error processing %s from %s; "
829				  "unable to evaluate Client Identifier",
830				  dhcpv6_type_names[packet->dhcpv6_msg_type],
831				  piaddr(packet->client_addr));
832			goto exit;
833	}
834
835	oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
836	if (oc != NULL) {
837		if (evaluate_option_cache(&lq->server_id, packet, NULL, NULL,
838					  packet->options, NULL,
839					  &global_scope, oc, MDL)) {
840			log_debug("Discarding %s from %s; "
841				  "server identifier found "
842				  "(CLIENTID %s, SERVERID %s)",
843				  dhcpv6_type_names[packet->dhcpv6_msg_type],
844				  piaddr(packet->client_addr),
845				  print_hex_1(lq->client_id.len,
846				  	      lq->client_id.data, 60),
847				  print_hex_2(lq->server_id.len,
848				  	      lq->server_id.data, 60));
849		} else {
850			log_debug("Discarding %s from %s; "
851				  "server identifier found "
852				  "(CLIENTID %s)",
853				  dhcpv6_type_names[packet->dhcpv6_msg_type],
854				  print_hex_1(lq->client_id.len,
855				  	      lq->client_id.data, 60),
856				  piaddr(packet->client_addr));
857		}
858		goto exit;
859	}
860
861	switch (get_lq_query(lq)) {
862		case ISC_R_SUCCESS:
863			break;
864		case ISC_R_NOTFOUND:
865			log_debug("Discarding %s from %s; lq-query missing",
866				  dhcpv6_type_names[packet->dhcpv6_msg_type],
867				  piaddr(packet->client_addr));
868			goto exit;
869		default:
870			log_error("Error processing %s from %s; "
871				  "unable to evaluate LQ-Query",
872				  dhcpv6_type_names[packet->dhcpv6_msg_type],
873				  piaddr(packet->client_addr));
874			goto exit;
875	}
876
877	/* looks good */
878	ret_val = 1;
879
880exit:
881	if (!ret_val) {
882		data_string_forget(&lq->client_id, MDL);
883		data_string_forget(&lq->server_id, MDL);
884		data_string_forget(&lq->lq_query, MDL);
885	}
886	return ret_val;
887}
888
889/*
890 * Set an error in a status-code option (from set_status_code).
891 */
892static int
893set_error(struct lq6_state *lq, u_int16_t code, const char *message) {
894	struct data_string d;
895	int ret_val;
896
897	memset(&d, 0, sizeof(d));
898	d.len = sizeof(code) + strlen(message);
899	if (!buffer_allocate(&d.buffer, d.len, MDL)) {
900		log_fatal("set_error: no memory for status code.");
901	}
902	d.data = d.buffer->data;
903	putUShort(d.buffer->data, code);
904	memcpy(d.buffer->data + sizeof(code), message, d.len - sizeof(code));
905	if (!save_option_buffer(&dhcpv6_universe, lq->reply_opts,
906				d.buffer, (unsigned char *)d.data, d.len,
907				D6O_STATUS_CODE, 0)) {
908		log_error("set_error: error saving status code.");
909		ret_val = 0;
910	} else {
911		ret_val = 1;
912	}
913	data_string_forget(&d, MDL);
914	return ret_val;
915}
916
917/*
918 * Process a by-address lease query.
919 */
920static int
921process_lq_by_address(struct lq6_state *lq) {
922	struct packet *packet = lq->packet;
923	struct option_cache *oc;
924	struct ipv6_pool *pool = NULL;
925	struct data_string data;
926	struct in6_addr addr;
927	struct iasubopt *iaaddr = NULL;
928	struct option_state *opt_state = NULL;
929	u_int32_t lifetime;
930	unsigned opt_cursor;
931	int ret_val = 0;
932
933	/*
934	 * Get the IAADDR.
935	 */
936	oc = lookup_option(&dhcpv6_universe, lq->query_opts, D6O_IAADDR);
937	if (oc == NULL) {
938		if (!set_error(lq, STATUS_MalformedQuery,
939			       "No OPTION_IAADDR.")) {
940			log_error("process_lq_by_address: unable "
941				  "to set MalformedQuery status code.");
942			return 0;
943		}
944		return 1;
945	}
946	memset(&data, 0, sizeof(data));
947	if (!evaluate_option_cache(&data, packet,
948				   NULL, NULL,
949				   lq->query_opts, NULL,
950				   &global_scope, oc, MDL) ||
951	    (data.len < IAADDR_OFFSET)) {
952		log_error("process_lq_by_address: error evaluating IAADDR.");
953		goto exit;
954	}
955	memcpy(&addr, data.data, sizeof(addr));
956	data_string_forget(&data, MDL);
957
958	/*
959	 * Find the lease.
960	 * Note the RFC 5007 says to use the link-address to find the link
961	 * or the ia-aadr when it is :: but in any case the ia-addr has
962	 * to be on the link, so we ignore the link-address here.
963	 */
964	if (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_SUCCESS) {
965		if (!set_error(lq, STATUS_NotConfigured,
966			       "Address not in a pool.")) {
967			log_error("process_lq_by_address: unable "
968				  "to set NotConfigured status code.");
969			goto exit;
970		}
971		ret_val = 1;
972		goto exit;
973	}
974	if (iasubopt_hash_lookup(&iaaddr, pool->leases, &addr,
975				 sizeof(addr), MDL) == 0) {
976		ret_val = 1;
977		goto exit;
978	}
979	if ((iaaddr == NULL) || (iaaddr->state != FTS_ACTIVE) ||
980	    (iaaddr->ia == NULL) || (iaaddr->ia->iaid_duid.len <= 4)) {
981		ret_val = 1;
982		goto exit;
983	}
984
985	/*
986	 * Build the client-data option (with client-id, ia-addr and clt-time).
987	 */
988	if (!option_state_allocate(&opt_state, MDL)) {
989		log_error("process_lq_by_address: "
990			  "no memory for option state.");
991		goto exit;
992	}
993
994	data_string_copy(&data, &iaaddr->ia->iaid_duid, MDL);
995	data.data += 4;
996	data.len -= 4;
997	if (!save_option_buffer(&dhcpv6_universe, opt_state,
998				NULL, (unsigned char *)data.data, data.len,
999				D6O_CLIENTID, 0)) {
1000		log_error("process_lq_by_address: error saving client ID.");
1001		goto exit;
1002	}
1003	data_string_forget(&data, MDL);
1004
1005	data.len = IAADDR_OFFSET;
1006	if (!buffer_allocate(&data.buffer, data.len, MDL)) {
1007		log_error("process_lq_by_address: no memory for ia-addr.");
1008		goto exit;
1009	}
1010	data.data = data.buffer->data;
1011	memcpy(data.buffer->data, &iaaddr->addr, 16);
1012	lifetime = iaaddr->prefer;
1013	putULong(data.buffer->data + 16, lifetime);
1014	lifetime = iaaddr->valid;
1015	putULong(data.buffer->data + 20, lifetime);
1016	if (!save_option_buffer(&dhcpv6_universe, opt_state,
1017				NULL, (unsigned char *)data.data, data.len,
1018				D6O_IAADDR, 0)) {
1019		log_error("process_lq_by_address: error saving ia-addr.");
1020		goto exit;
1021	}
1022	data_string_forget(&data, MDL);
1023
1024	lifetime = htonl(iaaddr->ia->cltt);
1025	if (!save_option_buffer(&dhcpv6_universe, opt_state,
1026				NULL, (unsigned char *)&lifetime, 4,
1027				D6O_CLT_TIME, 0)) {
1028		log_error("process_lq_by_address: error saving clt time.");
1029		goto exit;
1030	}
1031
1032	/*
1033	 * Store the client-data option.
1034	 */
1035	opt_cursor = lq->cursor;
1036	putUShort(lq->buf.data + lq->cursor, (unsigned)D6O_CLIENT_DATA);
1037	lq->cursor += 2;
1038	/* Skip option length. */
1039	lq->cursor += 2;
1040
1041	lq->cursor += store_options6((char *)lq->buf.data + lq->cursor,
1042				     sizeof(lq->buf) - lq->cursor,
1043				     opt_state, lq->packet,
1044				     required_opt_CLIENT_DATA, NULL);
1045	/* Reset the length. */
1046	putUShort(lq->buf.data + opt_cursor + 2,
1047		  lq->cursor - (opt_cursor + 4));
1048
1049	/* Done. */
1050	ret_val = 1;
1051
1052     exit:
1053	if (data.data != NULL)
1054		data_string_forget(&data, MDL);
1055	if (pool != NULL)
1056		ipv6_pool_dereference(&pool, MDL);
1057	if (iaaddr != NULL)
1058		iasubopt_dereference(&iaaddr, MDL);
1059	if (opt_state != NULL)
1060		option_state_dereference(&opt_state, MDL);
1061	return ret_val;
1062}
1063
1064
1065/*
1066 * Process a lease query.
1067 */
1068void
1069dhcpv6_leasequery(struct data_string *reply_ret, struct packet *packet) {
1070	static struct lq6_state lq;
1071	struct option_cache *oc;
1072	int allow_lq;
1073
1074	/*
1075	 * Initialize the lease query state.
1076	 */
1077	lq.packet = NULL;
1078	memset(&lq.client_id, 0, sizeof(lq.client_id));
1079	memset(&lq.server_id, 0, sizeof(lq.server_id));
1080	memset(&lq.lq_query, 0, sizeof(lq.lq_query));
1081	lq.query_opts = NULL;
1082	lq.reply_opts = NULL;
1083	packet_reference(&lq.packet, packet, MDL);
1084
1085	/*
1086	 * Validate our input.
1087	 */
1088	if (!valid_query_msg(&lq)) {
1089		goto exit;
1090	}
1091
1092	/*
1093	 * Prepare our reply.
1094	 */
1095	if (!option_state_allocate(&lq.reply_opts, MDL)) {
1096		log_error("dhcpv6_leasequery: no memory for option state.");
1097		goto exit;
1098	}
1099	execute_statements_in_scope(NULL, lq.packet, NULL, NULL,
1100				    lq.packet->options, lq.reply_opts,
1101				    &global_scope, root_group, NULL, NULL);
1102
1103	lq.buf.reply.msg_type = DHCPV6_LEASEQUERY_REPLY;
1104
1105	memcpy(lq.buf.reply.transaction_id,
1106	       lq.packet->dhcpv6_transaction_id,
1107	       sizeof(lq.buf.reply.transaction_id));
1108
1109	/*
1110	 * Because LEASEQUERY has some privacy concerns, default to deny.
1111	 */
1112	allow_lq = 0;
1113
1114	/*
1115	 * See if we are authorized to do LEASEQUERY.
1116	 */
1117	oc = lookup_option(&server_universe, lq.reply_opts, SV_LEASEQUERY);
1118	if (oc != NULL) {
1119		allow_lq = evaluate_boolean_option_cache(NULL,
1120							 lq.packet,
1121							 NULL, NULL,
1122							 lq.packet->options,
1123							 lq.reply_opts,
1124							 &global_scope,
1125							 oc, MDL);
1126	}
1127
1128	if (!allow_lq) {
1129		log_info("dhcpv6_leasequery: not allowed, query ignored.");
1130		goto exit;
1131	}
1132
1133	/*
1134	 * Same than transmission of REPLY message in RFC 3315:
1135	 *  server-id
1136	 *  client-id
1137	 */
1138
1139	oc = lookup_option(&dhcpv6_universe, lq.reply_opts, D6O_SERVERID);
1140	if (oc == NULL) {
1141		/* If not already in options, get from query then global. */
1142		if (lq.server_id.data == NULL)
1143			copy_server_duid(&lq.server_id, MDL);
1144		if (!save_option_buffer(&dhcpv6_universe,
1145					lq.reply_opts,
1146					NULL,
1147					(unsigned char *)lq.server_id.data,
1148					lq.server_id.len,
1149					D6O_SERVERID,
1150					0)) {
1151			log_error("dhcpv6_leasequery: "
1152				  "error saving server identifier.");
1153			goto exit;
1154		}
1155	}
1156
1157	if (!save_option_buffer(&dhcpv6_universe,
1158				lq.reply_opts,
1159				lq.client_id.buffer,
1160				(unsigned char *)lq.client_id.data,
1161				lq.client_id.len,
1162				D6O_CLIENTID,
1163				0)) {
1164		log_error("dhcpv6_leasequery: "
1165			  "error saving client identifier.");
1166		goto exit;
1167	}
1168
1169	lq.cursor = 4;
1170
1171	/*
1172	 * Decode the lq-query option.
1173	 */
1174
1175	if (lq.lq_query.len <= LQ_QUERY_OFFSET) {
1176		if (!set_error(&lq, STATUS_MalformedQuery,
1177			       "OPTION_LQ_QUERY too short.")) {
1178			log_error("dhcpv6_leasequery: unable "
1179				  "to set MalformedQuery status code.");
1180			goto exit;
1181		}
1182		goto done;
1183	}
1184
1185	lq.query_type = lq.lq_query.data [0];
1186	memcpy(&lq.link_addr, lq.lq_query.data + 1, sizeof(lq.link_addr));
1187	switch (lq.query_type) {
1188		case LQ6QT_BY_ADDRESS:
1189			break;
1190		case LQ6QT_BY_CLIENTID:
1191			if (!set_error(&lq, STATUS_UnknownQueryType,
1192				       "QUERY_BY_CLIENTID not supported.")) {
1193				log_error("dhcpv6_leasequery: unable to "
1194					  "set UnknownQueryType status code.");
1195				goto exit;
1196			}
1197			goto done;
1198		default:
1199			if (!set_error(&lq, STATUS_UnknownQueryType,
1200				       "Unknown query-type.")) {
1201				log_error("dhcpv6_leasequery: unable to "
1202					  "set UnknownQueryType status code.");
1203				goto exit;
1204			}
1205			goto done;
1206	}
1207
1208	if (!option_state_allocate(&lq.query_opts, MDL)) {
1209		log_error("dhcpv6_leasequery: no memory for option state.");
1210		goto exit;
1211	}
1212	if (!parse_option_buffer(lq.query_opts,
1213				 lq.lq_query.data + LQ_QUERY_OFFSET,
1214				 lq.lq_query.len - LQ_QUERY_OFFSET,
1215				 &dhcpv6_universe)) {
1216		log_error("dhcpv6_leasequery: error parsing query-options.");
1217		if (!set_error(&lq, STATUS_MalformedQuery,
1218			       "Bad query-options.")) {
1219			log_error("dhcpv6_leasequery: unable "
1220				  "to set MalformedQuery status code.");
1221			goto exit;
1222		}
1223		goto done;
1224	}
1225
1226	/* Do it. */
1227	if (!process_lq_by_address(&lq))
1228		goto exit;
1229
1230      done:
1231	/* Store the options. */
1232	lq.cursor += store_options6((char *)lq.buf.data + lq.cursor,
1233				    sizeof(lq.buf) - lq.cursor,
1234				    lq.reply_opts,
1235				    lq.packet,
1236				    required_opts_lq,
1237				    NULL);
1238
1239	/* Return our reply to the caller. */
1240	reply_ret->len = lq.cursor;
1241	reply_ret->buffer = NULL;
1242	if (!buffer_allocate(&reply_ret->buffer, lq.cursor, MDL)) {
1243		log_fatal("dhcpv6_leasequery: no memory to store Reply.");
1244	}
1245	memcpy(reply_ret->buffer->data, lq.buf.data, lq.cursor);
1246	reply_ret->data = reply_ret->buffer->data;
1247
1248      exit:
1249	/* Cleanup. */
1250	if (lq.packet != NULL)
1251		packet_dereference(&lq.packet, MDL);
1252	if (lq.client_id.data != NULL)
1253		data_string_forget(&lq.client_id, MDL);
1254	if (lq.server_id.data != NULL)
1255		data_string_forget(&lq.server_id, MDL);
1256	if (lq.lq_query.data != NULL)
1257		data_string_forget(&lq.lq_query, MDL);
1258	if (lq.query_opts != NULL)
1259		option_state_dereference(&lq.query_opts, MDL);
1260	if (lq.reply_opts != NULL)
1261		option_state_dereference(&lq.reply_opts, MDL);
1262}
1263
1264#endif /* DHCPv6 */
1265