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