dhcpleasequery.c revision 1.3
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