1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. 23 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>. 24 * Copyright 2019 Joshua M. Clulow <josh@sysmgr.org> 25 * Copyright 2026 Oxide Computer Company 26 */ 27 28 #include <unistd.h> 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 #include <sys/utsname.h> 32 #include <stdlib.h> 33 #include <netinet/in.h> /* struct in_addr */ 34 #include <netinet/dhcp.h> 35 #include <inet/ip.h> 36 #include <signal.h> 37 #include <sys/socket.h> 38 #include <net/route.h> 39 #include <net/if_arp.h> 40 #include <string.h> 41 #include <dhcpmsg.h> 42 #include <ctype.h> 43 #include <arpa/inet.h> 44 #include <arpa/nameser.h> 45 #include <resolv.h> 46 #include <netdb.h> 47 #include <fcntl.h> 48 #include <stdio.h> 49 #include <dhcp_hostconf.h> 50 #include <dhcp_inittab.h> 51 #include <dhcp_symbol.h> 52 #include <limits.h> 53 #include <strings.h> 54 #include <libipadm.h> 55 56 #include "states.h" 57 #include "agent.h" 58 #include "interface.h" 59 #include "util.h" 60 #include "packet.h" 61 #include "defaults.h" 62 63 /* 64 * this file contains utility functions that have no real better home 65 * of their own. they can largely be broken into six categories: 66 * 67 * o conversion functions -- functions to turn integers into strings, 68 * or to convert between units of a similar measure. 69 * 70 * o time and timer functions -- functions to handle time measurement 71 * and events. 72 * 73 * o ipc-related functions -- functions to simplify the generation of 74 * ipc messages to the agent's clients. 75 * 76 * o signal-related functions -- functions to clean up the agent when 77 * it receives a signal. 78 * 79 * o routing table manipulation functions 80 * 81 * o true miscellany -- anything else 82 */ 83 84 #define ETCNODENAME "/etc/nodename" 85 86 static boolean_t is_fqdn(const char *); 87 static boolean_t dhcp_assemble_fqdn(char *fqdnbuf, size_t buflen, 88 dhcp_smach_t *dsmp); 89 90 /* 91 * pkt_type_to_string(): stringifies a packet type 92 * 93 * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315 94 * boolean_t: B_TRUE if IPv6 95 * output: const char *: the stringified packet type 96 */ 97 98 const char * 99 pkt_type_to_string(uchar_t type, boolean_t isv6) 100 { 101 /* 102 * note: the ordering in these arrays allows direct indexing of the 103 * table based on the RFC packet type value passed in. 104 */ 105 106 static const char *v4types[] = { 107 "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE", 108 "ACK", "NAK", "RELEASE", "INFORM" 109 }; 110 static const char *v6types[] = { 111 NULL, "SOLICIT", "ADVERTISE", "REQUEST", 112 "CONFIRM", "RENEW", "REBIND", "REPLY", 113 "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST", 114 "RELAY-FORW", "RELAY-REPL" 115 }; 116 117 if (isv6) { 118 if (type >= sizeof (v6types) / sizeof (*v6types) || 119 v6types[type] == NULL) 120 return ("<unknown>"); 121 else 122 return (v6types[type]); 123 } else { 124 if (type >= sizeof (v4types) / sizeof (*v4types) || 125 v4types[type] == NULL) 126 return ("<unknown>"); 127 else 128 return (v4types[type]); 129 } 130 } 131 132 /* 133 * monosec_to_string(): converts a monosec_t into a date string 134 * 135 * input: monosec_t: the monosec_t to convert 136 * output: const char *: the corresponding date string 137 */ 138 139 const char * 140 monosec_to_string(monosec_t monosec) 141 { 142 time_t time = monosec_to_time(monosec); 143 char *time_string = ctime(&time); 144 145 /* strip off the newline -- ugh, why, why, why.. */ 146 time_string[strlen(time_string) - 1] = '\0'; 147 return (time_string); 148 } 149 150 /* 151 * monosec(): returns a monotonically increasing time in seconds that 152 * is not affected by stime(2) or adjtime(2). 153 * 154 * input: void 155 * output: monosec_t: the number of seconds since some time in the past 156 */ 157 158 monosec_t 159 monosec(void) 160 { 161 return (gethrtime() / NANOSEC); 162 } 163 164 /* 165 * monosec_to_time(): converts a monosec_t into real wall time 166 * 167 * input: monosec_t: the absolute monosec_t to convert 168 * output: time_t: the absolute time that monosec_t represents in wall time 169 */ 170 171 time_t 172 monosec_to_time(monosec_t abs_monosec) 173 { 174 return (abs_monosec - monosec()) + time(NULL); 175 } 176 177 /* 178 * hrtime_to_monosec(): converts a hrtime_t to monosec_t 179 * 180 * input: hrtime_t: the time to convert 181 * output: monosec_t: the time in monosec_t 182 */ 183 184 monosec_t 185 hrtime_to_monosec(hrtime_t hrtime) 186 { 187 return (hrtime / NANOSEC); 188 } 189 190 /* 191 * print_server_msg(): prints a message from a DHCP server 192 * 193 * input: dhcp_smach_t *: the state machine the message is associated with 194 * const char *: the string to display 195 * uint_t: length of string 196 * output: void 197 */ 198 199 void 200 print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen) 201 { 202 if (msglen > 0) { 203 dhcpmsg(MSG_INFO, "%s: message from server: %.*s", 204 dsmp->dsm_name, msglen, msg); 205 } 206 } 207 208 /* 209 * alrm_exit(): Signal handler for SIGARLM. terminates grandparent. 210 * 211 * input: int: signal the handler was called with. 212 * 213 * output: void 214 */ 215 216 static void 217 alrm_exit(int sig) 218 { 219 int exitval; 220 221 if (sig == SIGALRM && grandparent != 0) 222 exitval = EXIT_SUCCESS; 223 else 224 exitval = EXIT_FAILURE; 225 226 _exit(exitval); 227 } 228 229 /* 230 * daemonize(): daemonizes the process 231 * 232 * input: void 233 * output: int: 1 on success, 0 on failure 234 */ 235 236 int 237 daemonize(void) 238 { 239 /* 240 * We've found that adoption takes sufficiently long that 241 * a dhcpinfo run after dhcpagent -a is started may occur 242 * before the agent is ready to process the request. 243 * The result is an error message and an unhappy user. 244 * 245 * The initial process now sleeps for DHCP_ADOPT_SLEEP, 246 * unless interrupted by a SIGALRM, in which case it 247 * exits immediately. This has the effect that the 248 * grandparent doesn't exit until the dhcpagent is ready 249 * to process requests. This defers the the balance of 250 * the system start-up script processing until the 251 * dhcpagent is ready to field requests. 252 * 253 * grandparent is only set for the adopt case; other 254 * cases do not require the wait. 255 */ 256 257 if (grandparent != 0) 258 (void) signal(SIGALRM, alrm_exit); 259 260 switch (fork()) { 261 262 case -1: 263 return (0); 264 265 case 0: 266 if (grandparent != 0) 267 (void) signal(SIGALRM, SIG_DFL); 268 269 /* 270 * setsid() makes us lose our controlling terminal, 271 * and become both a session leader and a process 272 * group leader. 273 */ 274 275 (void) setsid(); 276 277 /* 278 * under POSIX, a session leader can accidentally 279 * (through open(2)) acquire a controlling terminal if 280 * it does not have one. just to be safe, fork again 281 * so we are not a session leader. 282 */ 283 284 switch (fork()) { 285 286 case -1: 287 return (0); 288 289 case 0: 290 (void) signal(SIGHUP, SIG_IGN); 291 (void) chdir("/"); 292 (void) umask(022); 293 closefrom(0); 294 break; 295 296 default: 297 _exit(EXIT_SUCCESS); 298 } 299 break; 300 301 default: 302 if (grandparent != 0) { 303 (void) signal(SIGCHLD, SIG_IGN); 304 /* 305 * Note that we're not the agent here, so the DHCP 306 * logging subsystem hasn't been configured yet. 307 */ 308 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " 309 "waiting for adoption to complete."); 310 if (sleep(DHCP_ADOPT_SLEEP) == 0) { 311 syslog(LOG_WARNING | LOG_DAEMON, 312 "dhcpagent: daemonize: timed out awaiting " 313 "adoption."); 314 } 315 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " 316 "wait finished"); 317 } 318 _exit(EXIT_SUCCESS); 319 } 320 321 return (1); 322 } 323 324 /* 325 * update_route(): update a configured route on an interface 326 * 327 * input: uint32_t: index of the interface associated with the route 328 * int: the type of message; either RTM_ADD or RTM_DELETE 329 * dhcp_route_t *: the route to use 330 * int: any additional flags (besides RTF_STATIC and RTF_GATEWAY) 331 * output: boolean_t: B_TRUE on success, B_FALSE on failure 332 */ 333 334 static boolean_t 335 update_route(uint32_t ifindex, int type, dhcp_route_t *dhr, int flags) 336 { 337 struct { 338 struct rt_msghdr rm_mh; 339 struct sockaddr_in rm_dst; 340 struct sockaddr_in rm_gw; 341 struct sockaddr_in rm_mask; 342 struct sockaddr_dl rm_ifp; 343 } rtmsg; 344 345 (void) memset(&rtmsg, 0, sizeof (rtmsg)); 346 rtmsg.rm_mh.rtm_version = RTM_VERSION; 347 rtmsg.rm_mh.rtm_msglen = sizeof (rtmsg); 348 rtmsg.rm_mh.rtm_type = type; 349 rtmsg.rm_mh.rtm_pid = getpid(); 350 rtmsg.rm_mh.rtm_flags = flags; 351 rtmsg.rm_mh.rtm_addrs = RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP; 352 353 /* 354 * If this is not an interface route, add the flags for a route with a 355 * next hop. 356 */ 357 if (!dhr->dhr_interface) { 358 rtmsg.rm_mh.rtm_flags |= RTF_GATEWAY | RTF_STATIC; 359 } 360 361 if (dhr->dhr_prefix == IPV4_ABITS) { 362 rtmsg.rm_mh.rtm_flags |= RTF_HOST; 363 } 364 365 /* 366 * Note that for an interface route, the next hop address will have 367 * been set to the interface address when we were processing the option 368 * list. 369 */ 370 rtmsg.rm_gw.sin_family = AF_INET; 371 rtmsg.rm_gw.sin_addr = dhr->dhr_nexthop; 372 373 rtmsg.rm_dst.sin_family = AF_INET; 374 rtmsg.rm_dst.sin_addr = dhr->dhr_network; 375 376 rtmsg.rm_mask.sin_family = AF_INET; 377 rtmsg.rm_mask.sin_addr.s_addr = dhr->dhr_prefix == 0 ? 0 : 378 htonl(~0U << (IPV4_ABITS - dhr->dhr_prefix)); 379 380 rtmsg.rm_ifp.sdl_family = AF_LINK; 381 rtmsg.rm_ifp.sdl_index = ifindex; 382 383 return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg)); 384 } 385 386 /* 387 * add_route(): add a route for given destination prefix and gateway 388 * 389 * input: uint32_t: index of the interface associated with the route 390 * dhcp_route_t: the route to add 391 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 392 */ 393 394 boolean_t 395 add_route(uint32_t ifindex, dhcp_route_t *route) 396 { 397 return (update_route(ifindex, RTM_ADD, route, RTF_UP)); 398 } 399 400 /* 401 * del_route(): deletes a route to the given destination prefix and gateway 402 * 403 * input: uint32_t: the index of the interface associated with the route 404 * dhcp_route_t: the route to remove 405 * output: boolean_t: B_TRUE on success, B_FALSE on failure 406 */ 407 408 boolean_t 409 del_route(uint32_t ifindex, dhcp_route_t *route) 410 { 411 if (!route->dhr_installed) { 412 /* 413 * We never installed this route. 414 */ 415 return (B_TRUE); 416 } 417 418 return (update_route(ifindex, RTM_DELETE, route, 0)); 419 } 420 421 /* 422 * inactivity_shutdown(): shuts down agent if there are no state machines left 423 * to manage 424 * 425 * input: iu_tq_t *: unused 426 * void *: unused 427 * output: void 428 */ 429 430 /* ARGSUSED */ 431 void 432 inactivity_shutdown(iu_tq_t *tqp, void *arg) 433 { 434 if (smach_count() > 0) /* shouldn't happen, but... */ 435 return; 436 437 dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out"); 438 439 iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL); 440 } 441 442 /* 443 * graceful_shutdown(): shuts down the agent gracefully 444 * 445 * input: int: the signal that caused graceful_shutdown to be called 446 * output: void 447 */ 448 449 void 450 graceful_shutdown(int sig) 451 { 452 iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE : 453 DHCP_REASON_SIGNAL), drain_script, NULL); 454 } 455 456 /* 457 * bind_sock(): binds a socket to a given IP address and port number 458 * 459 * input: int: the socket to bind 460 * in_port_t: the port number to bind to, host byte order 461 * in_addr_t: the address to bind to, host byte order 462 * output: boolean_t: B_TRUE on success, B_FALSE on failure 463 */ 464 465 boolean_t 466 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo) 467 { 468 struct sockaddr_in sin; 469 int on = 1; 470 471 (void) memset(&sin, 0, sizeof (struct sockaddr_in)); 472 sin.sin_family = AF_INET; 473 sin.sin_port = htons(port_hbo); 474 sin.sin_addr.s_addr = htonl(addr_hbo); 475 476 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); 477 478 return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0); 479 } 480 481 /* 482 * bind_sock_v6(): binds a socket to a given IP address and port number 483 * 484 * input: int: the socket to bind 485 * in_port_t: the port number to bind to, host byte order 486 * in6_addr_t: the address to bind to, network byte order 487 * output: boolean_t: B_TRUE on success, B_FALSE on failure 488 */ 489 490 boolean_t 491 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo) 492 { 493 struct sockaddr_in6 sin6; 494 int on = 1; 495 496 (void) memset(&sin6, 0, sizeof (struct sockaddr_in6)); 497 sin6.sin6_family = AF_INET6; 498 sin6.sin6_port = htons(port_hbo); 499 if (addr_nbo != NULL) { 500 (void) memcpy(&sin6.sin6_addr, addr_nbo, 501 sizeof (sin6.sin6_addr)); 502 } 503 504 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); 505 506 return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0); 507 } 508 509 /* 510 * iffile_to_hostname(): return the hostname contained on a line of the form 511 * 512 * [ ^I]*inet[ ^I]+hostname[\n]*\0 513 * 514 * in the file located at the specified path 515 * 516 * input: const char *: the path of the file to look in for the hostname 517 * output: const char *: the hostname at that path, or NULL on failure 518 */ 519 520 #define IFLINE_MAX 1024 /* maximum length of a hostname.<if> line */ 521 522 const char * 523 iffile_to_hostname(const char *path) 524 { 525 FILE *fp; 526 static char ifline[IFLINE_MAX]; 527 528 fp = fopen(path, "r"); 529 if (fp == NULL) 530 return (NULL); 531 532 /* 533 * /etc/hostname.<if> may contain multiple ifconfig commands, but each 534 * such command is on a separate line (see the "while read ifcmds" code 535 * in /etc/init.d/inetinit). Thus we will read the file a line at a 536 * time, searching for a line of the form 537 * 538 * [ ^I]*inet[ ^I]+hostname[\n]*\0 539 * 540 * extract the host name from it, and check it for validity. 541 */ 542 while (fgets(ifline, sizeof (ifline), fp) != NULL) { 543 char *p; 544 545 if ((p = strstr(ifline, "inet")) != NULL) { 546 if ((p != ifline) && !isspace(p[-1])) { 547 (void) fclose(fp); 548 return (NULL); 549 } 550 p += 4; /* skip over "inet" and expect spaces or tabs */ 551 if ((*p == '\n') || (*p == '\0')) { 552 (void) fclose(fp); 553 return (NULL); 554 } 555 if (isspace(*p)) { 556 char *nlptr; 557 558 /* no need to read more of the file */ 559 (void) fclose(fp); 560 561 while (isspace(*p)) 562 p++; 563 if ((nlptr = strrchr(p, '\n')) != NULL) 564 *nlptr = '\0'; 565 if (strlen(p) > MAXHOSTNAMELEN) { 566 dhcpmsg(MSG_WARNING, 567 "iffile_to_hostname:" 568 " host name too long"); 569 return (NULL); 570 } 571 if (ipadm_is_valid_hostname(p)) { 572 return (p); 573 } else { 574 dhcpmsg(MSG_WARNING, 575 "iffile_to_hostname:" 576 " host name not valid"); 577 return (NULL); 578 } 579 } else { 580 (void) fclose(fp); 581 return (NULL); 582 } 583 } 584 } 585 586 (void) fclose(fp); 587 return (NULL); 588 } 589 590 /* 591 * init_timer(): set up a DHCP timer 592 * 593 * input: dhcp_timer_t *: the timer to set up 594 * output: void 595 */ 596 597 void 598 init_timer(dhcp_timer_t *dt, lease_t startval) 599 { 600 dt->dt_id = -1; 601 dt->dt_start = startval; 602 } 603 604 /* 605 * cancel_timer(): cancel a DHCP timer 606 * 607 * input: dhcp_timer_t *: the timer to cancel 608 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 609 */ 610 611 boolean_t 612 cancel_timer(dhcp_timer_t *dt) 613 { 614 if (dt->dt_id == -1) 615 return (B_TRUE); 616 617 if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) { 618 dt->dt_id = -1; 619 return (B_TRUE); 620 } 621 622 return (B_FALSE); 623 } 624 625 /* 626 * schedule_timer(): schedule a DHCP timer. Note that it must not be already 627 * running, and that we can't cancel here. If it were, and 628 * we did, we'd leak a reference to the callback argument. 629 * 630 * input: dhcp_timer_t *: the timer to schedule 631 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 632 */ 633 634 boolean_t 635 schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg) 636 { 637 if (dt->dt_id != -1) 638 return (B_FALSE); 639 dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg); 640 return (dt->dt_id != -1); 641 } 642 643 /* 644 * dhcpv6_status_code(): report on a DHCPv6 status code found in an option 645 * buffer. 646 * 647 * input: const dhcpv6_option_t *: pointer to option 648 * uint_t: option length 649 * const char **: error string (nul-terminated) 650 * const char **: message from server (unterminated) 651 * uint_t *: length of server message 652 * output: int: -1 on error, or >= 0 for a DHCPv6 status code 653 */ 654 655 int 656 dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr, 657 const char **msg, uint_t *msglenp) 658 { 659 uint16_t status; 660 static const char *v6_status[] = { 661 NULL, 662 "Unknown reason", 663 "Server has no addresses available", 664 "Client record unavailable", 665 "Prefix inappropriate for link", 666 "Client must use multicast", 667 "No prefix available" 668 }; 669 static char sbuf[32]; 670 671 *estr = ""; 672 *msg = ""; 673 *msglenp = 0; 674 if (d6o == NULL) 675 return (0); 676 olen -= sizeof (*d6o); 677 if (olen < 2) { 678 *estr = "garbled status code"; 679 return (-1); 680 } 681 682 *msg = (const char *)(d6o + 1) + 2; 683 *msglenp = olen - 2; 684 685 (void) memcpy(&status, d6o + 1, sizeof (status)); 686 status = ntohs(status); 687 if (status > 0) { 688 if (status > DHCPV6_STAT_NOPREFIX) { 689 (void) snprintf(sbuf, sizeof (sbuf), "status %u", 690 status); 691 *estr = sbuf; 692 } else { 693 *estr = v6_status[status]; 694 } 695 } 696 return (status); 697 } 698 699 void 700 write_lease_to_hostconf(dhcp_smach_t *dsmp) 701 { 702 PKT_LIST *plp[2]; 703 const char *hcfile; 704 705 hcfile = ifname_to_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); 706 plp[0] = dsmp->dsm_ack; 707 plp[1] = dsmp->dsm_orig_ack; 708 if (write_hostconf(dsmp->dsm_name, plp, 2, 709 monosec_to_time(dsmp->dsm_curstart_monosec), 710 dsmp->dsm_isv6) != -1) { 711 dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile); 712 } else if (errno == EROFS) { 713 dhcpmsg(MSG_DEBUG, "%s is on a read-only file " 714 "system; not saving lease", hcfile); 715 } else { 716 dhcpmsg(MSG_ERR, "cannot write %s (reboot will " 717 "not use cached configuration)", hcfile); 718 } 719 } 720 721 /* 722 * Try to get a string from the first line of a file, up to but not 723 * including any space (0x20) or newline. 724 * 725 * input: const char *: file name; 726 * char *: allocated buffer space; 727 * size_t: space available in buf; 728 * output: boolean_t: B_TRUE if a non-empty string was written to buf; 729 * B_FALSE otherwise. 730 */ 731 732 static boolean_t 733 dhcp_get_oneline(const char *filename, char *buf, size_t buflen) 734 { 735 char value[SYS_NMLN], *c; 736 int fd, i; 737 738 if ((fd = open(filename, O_RDONLY)) <= 0) { 739 dhcpmsg(MSG_DEBUG, "dhcp_get_oneline: could not open %s", 740 filename); 741 *buf = '\0'; 742 } else { 743 if ((i = read(fd, value, SYS_NMLN - 1)) <= 0) { 744 dhcpmsg(MSG_WARNING, "dhcp_get_oneline: no line in %s", 745 filename); 746 *buf = '\0'; 747 } else { 748 value[i] = '\0'; 749 if ((c = strchr(value, '\n')) != NULL) 750 *c = '\0'; 751 if ((c = strchr(value, ' ')) != NULL) 752 *c = '\0'; 753 754 if (strlcpy(buf, value, buflen) >= buflen) { 755 dhcpmsg(MSG_WARNING, "dhcp_get_oneline: too" 756 " long value, %s", value); 757 *buf = '\0'; 758 } 759 } 760 (void) close(fd); 761 } 762 763 return (*buf != '\0'); 764 } 765 766 /* 767 * Try to get the hostname from the /etc/nodename file. uname(2) cannot 768 * be used, because that is initialized after DHCP has solicited, in order 769 * to allow for the possibility that utsname.nodename can be set from 770 * DHCP Hostname. Here, though, we want to send a value specified 771 * advance of DHCP, so read /etc/nodename directly. 772 * 773 * input: char *: allocated buffer space; 774 * size_t: space available in buf; 775 * output: boolean_t: B_TRUE if a non-empty string was written to buf; 776 * B_FALSE otherwise. 777 */ 778 779 static boolean_t 780 dhcp_get_nodename(char *buf, size_t buflen) 781 { 782 return (dhcp_get_oneline(ETCNODENAME, buf, buflen)); 783 } 784 785 /* 786 * dhcp_add_hostname_opt(): Set CD_HOSTNAME option if REQUEST_HOSTNAME is 787 * affirmative and if 1) dsm_msg_reqhost is available; 788 * or 2) hostname is read from an extant 789 * /etc/hostname.<ifname> file; or 3) interface is 790 * primary and nodename(5) is defined. 791 * 792 * input: dhcp_pkt_t *: pointer to DHCP message being constructed; 793 * dhcp_smach_t *: pointer to interface DHCP state machine; 794 * output: B_TRUE if a client hostname was added; B_FALSE otherwise. 795 */ 796 797 boolean_t 798 dhcp_add_hostname_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp) 799 { 800 const char *reqhost; 801 char nodename[MAXNAMELEN]; 802 803 if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_HOSTNAME)) 804 return (B_FALSE); 805 806 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: DF_REQUEST_HOSTNAME"); 807 808 if (dsmp->dsm_msg_reqhost != NULL && 809 ipadm_is_valid_hostname(dsmp->dsm_msg_reqhost)) { 810 reqhost = dsmp->dsm_msg_reqhost; 811 } else { 812 char hostfile[PATH_MAX + 1]; 813 814 (void) snprintf(hostfile, sizeof (hostfile), 815 "/etc/hostname.%s", dsmp->dsm_name); 816 reqhost = iffile_to_hostname(hostfile); 817 } 818 819 if (reqhost == NULL && (dsmp->dsm_dflags & DHCP_IF_PRIMARY) && 820 dhcp_get_nodename(nodename, sizeof (nodename))) { 821 reqhost = nodename; 822 } 823 824 if (reqhost != NULL) { 825 free(dsmp->dsm_reqhost); 826 if ((dsmp->dsm_reqhost = strdup(reqhost)) == NULL) 827 dhcpmsg(MSG_WARNING, "dhcp_add_hostname_opt: cannot" 828 " allocate memory for host name option"); 829 } 830 831 if (dsmp->dsm_reqhost != NULL) { 832 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: host %s for %s", 833 dsmp->dsm_reqhost, dsmp->dsm_name); 834 (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, 835 strlen(dsmp->dsm_reqhost)); 836 return (B_FALSE); 837 } else { 838 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: no hostname for %s", 839 dsmp->dsm_name); 840 } 841 842 return (B_TRUE); 843 } 844 845 /* 846 * dhcp_add_fqdn_opt(): Set client FQDN option if dhcp_assemble_fqdn() 847 * initializes an FQDN, or else do nothing. 848 * 849 * input: dhcp_pkt_t *: pointer to DHCP message being constructed; 850 * dhcp_smach_t *: pointer to interface DHCP state machine; 851 * output: B_TRUE if a client FQDN was added; B_FALSE otherwise. 852 */ 853 854 boolean_t 855 dhcp_add_fqdn_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp) 856 { 857 /* 858 * RFC 4702 section 2: 859 * 860 * The format of the Client FQDN option is: 861 * 862 * Code Len Flags RCODE1 RCODE2 Domain Name 863 * +------+------+------+------+------+------+-- 864 * | 81 | n | | | | ... 865 * +------+------+------+------+------+------+-- 866 * 867 * Code and Len are distinct, and the remainder is in a single buffer, 868 * opt81, for Flags + (unused) RCODE1 and RCODE2 (all octets) and a 869 * potentially maximum-length domain name. 870 * 871 * The format of the Flags field is: 872 * 873 * 0 1 2 3 4 5 6 7 874 * +-+-+-+-+-+-+-+-+ 875 * | MBZ |N|E|O|S| 876 * +-+-+-+-+-+-+-+-+ 877 * 878 * where MBZ is ignored and NEOS are: 879 * 880 * S = 1 to request that "the server SHOULD perform the A RR (FQDN-to- 881 * address) DNS updates; 882 * 883 * O = 0, for a server-only response bit; 884 * 885 * E = 1 to indicate the domain name is in "canonical wire format, 886 * without compression (i.e., ns_name_pton2) .... This encoding SHOULD 887 * be used by clients ...."; 888 * 889 * N = 0 to request that "the server SHALL perform DNS updates [of the 890 * PTR RR]." (1 would request SHALL NOT update). 891 */ 892 893 const uint8_t S_BIT_POS = 7; 894 const uint8_t E_BIT_POS = 5; 895 const uint8_t S_BIT = 1 << (7 - S_BIT_POS); 896 const uint8_t E_BIT = 1 << (7 - E_BIT_POS); 897 const size_t OPT_FQDN_METALEN = 3; 898 char fqdnbuf[MAXNAMELEN]; 899 uchar_t enc_fqdnbuf[MAXNAMELEN]; 900 uint8_t fqdnopt[MAXNAMELEN + OPT_FQDN_METALEN]; 901 uint_t fqdncode; 902 size_t len, metalen; 903 904 if (dsmp->dsm_isv6) 905 return (B_FALSE); 906 907 if (!dhcp_assemble_fqdn(fqdnbuf, sizeof (fqdnbuf), dsmp)) 908 return (B_FALSE); 909 910 /* encode the FQDN in canonical wire format */ 911 912 if (ns_name_pton2(fqdnbuf, enc_fqdnbuf, sizeof (enc_fqdnbuf), 913 &len) < 0) { 914 dhcpmsg(MSG_WARNING, "dhcp_add_fqdn_opt: error encoding domain" 915 " name %s", fqdnbuf); 916 return (B_FALSE); 917 } 918 919 dhcpmsg(MSG_DEBUG, "dhcp_add_fqdn_opt: interface FQDN is %s" 920 " for %s", fqdnbuf, dsmp->dsm_name); 921 922 bzero(fqdnopt, sizeof (fqdnopt)); 923 fqdncode = CD_CLIENTFQDN; 924 metalen = OPT_FQDN_METALEN; 925 *fqdnopt = S_BIT | E_BIT; 926 (void) memcpy(fqdnopt + metalen, enc_fqdnbuf, len); 927 (void) add_pkt_opt(dpkt, fqdncode, fqdnopt, metalen + len); 928 929 return (B_TRUE); 930 } 931 932 /* 933 * dhcp_adopt_domainname(): Set namebuf if either dsm_dhcp_domainname or 934 * resolv's "default domain (deprecated)" is defined. 935 * 936 * input: char *: pointer to buffer to which domain name will be written; 937 * size_t length of buffer; 938 * dhcp_smach_t *: pointer to interface DHCP state machine; 939 * output: B_TRUE if namebuf was set to a valid domain name; B_FALSE 940 * otherwise. 941 */ 942 943 static boolean_t 944 dhcp_adopt_domainname(char *namebuf, size_t buflen, dhcp_smach_t *dsmp) 945 { 946 const char *domainname; 947 struct __res_state res_state; 948 int lasterrno; 949 950 domainname = dsmp->dsm_dhcp_domainname; 951 952 if (ipadm_is_nil_hostname(domainname)) { 953 /* 954 * fall back to resolv's "default domain (deprecated)" 955 */ 956 bzero(&res_state, sizeof (struct __res_state)); 957 958 if ((lasterrno = res_ninit(&res_state)) != 0) { 959 dhcpmsg(MSG_WARNING, "dhcp_adopt_domainname: error %d" 960 " initializing resolver", lasterrno); 961 return (B_FALSE); 962 } 963 964 domainname = NULL; 965 if (!ipadm_is_nil_hostname(res_state.defdname)) 966 domainname = res_state.defdname; 967 968 /* N.b. res_state.defdname survives the following call */ 969 res_ndestroy(&res_state); 970 } 971 972 if (domainname == NULL) 973 return (B_FALSE); 974 975 if (strlcpy(namebuf, domainname, buflen) >= buflen) { 976 dhcpmsg(MSG_WARNING, 977 "dhcp_adopt_domainname: too long adopted domain" 978 " name %s for %s", domainname, dsmp->dsm_name); 979 return (B_FALSE); 980 } 981 982 return (B_TRUE); 983 } 984 985 /* 986 * dhcp_pick_domainname(): Set namebuf if DNS_DOMAINNAME is defined in 987 * /etc/default/dhcpagent or if dhcp_adopt_domainname() 988 * succeeds. 989 * 990 * input: char *: pointer to buffer to which domain name will be written; 991 * size_t length of buffer; 992 * dhcp_smach_t *: pointer to interface DHCP state machine; 993 * output: B_TRUE if namebuf was set to a valid domain name; B_FALSE 994 * otherwise. 995 */ 996 997 static boolean_t 998 dhcp_pick_domainname(char *namebuf, size_t buflen, dhcp_smach_t *dsmp) 999 { 1000 const char *domainname; 1001 1002 /* 1003 * Try to use a static DNS_DOMAINNAME if defined in 1004 * /etc/default/dhcpagent. 1005 */ 1006 domainname = df_get_string(dsmp->dsm_name, dsmp->dsm_isv6, 1007 DF_DNS_DOMAINNAME); 1008 if (!ipadm_is_nil_hostname(domainname)) { 1009 if (strlcpy(namebuf, domainname, buflen) >= buflen) { 1010 dhcpmsg(MSG_WARNING, "dhcp_pick_domainname: too long" 1011 " DNS_DOMAINNAME %s for %s", domainname, 1012 dsmp->dsm_name); 1013 return (B_FALSE); 1014 } 1015 return (B_TRUE); 1016 } else if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, 1017 DF_ADOPT_DOMAINNAME)) { 1018 return (dhcp_adopt_domainname(namebuf, buflen, dsmp)); 1019 } else { 1020 return (B_FALSE); 1021 } 1022 } 1023 1024 /* 1025 * dhcp_assemble_fqdn(): Set fqdnbuf if REQUEST_FQDN is set and 1026 * either a host name was sent in the IPC message (e.g., 1027 * from ipadm(8) -h,--reqhost) or the interface is 1028 * primary and a nodename(5) is defined. If the host 1029 * name is not already fully qualified per is_fqdn(), 1030 * then dhcp_pick_domainname() is tried to select a 1031 * domain to be used to construct an FQDN. 1032 * 1033 * input: char *: pointer to buffer to which FQDN will be written; 1034 * size_t length of buffer; 1035 * dhcp_smach_t *: pointer to interface DHCP state machine; 1036 * output: B_TRUE if fqdnbuf was assigned a valid FQDN; B_FALSE otherwise. 1037 */ 1038 1039 static boolean_t 1040 dhcp_assemble_fqdn(char *fqdnbuf, size_t buflen, dhcp_smach_t *dsmp) 1041 { 1042 char nodename[MAXNAMELEN], *reqhost; 1043 size_t pos, len; 1044 1045 1046 if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_FQDN)) 1047 return (B_FALSE); 1048 1049 dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: DF_REQUEST_FQDN"); 1050 1051 /* It's convenient to ensure fqdnbuf is always null-terminated */ 1052 bzero(fqdnbuf, buflen); 1053 1054 reqhost = dsmp->dsm_msg_reqhost; 1055 if (ipadm_is_nil_hostname(reqhost) && 1056 (dsmp->dsm_dflags & DHCP_IF_PRIMARY) && 1057 dhcp_get_nodename(nodename, sizeof (nodename))) { 1058 reqhost = nodename; 1059 } 1060 1061 if (ipadm_is_nil_hostname(reqhost)) { 1062 dhcpmsg(MSG_DEBUG, 1063 "dhcp_assemble_fqdn: no interface reqhost for %s", 1064 dsmp->dsm_name); 1065 return (B_FALSE); 1066 } 1067 1068 if ((pos = strlcpy(fqdnbuf, reqhost, buflen)) >= buflen) { 1069 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long reqhost %s" 1070 " for %s", reqhost, dsmp->dsm_name); 1071 return (B_FALSE); 1072 } 1073 1074 /* 1075 * If not yet FQDN, construct if possible 1076 */ 1077 if (!is_fqdn(reqhost)) { 1078 char domainname[MAXNAMELEN]; 1079 size_t needdots; 1080 1081 if (!dhcp_pick_domainname(domainname, sizeof (domainname), 1082 dsmp)) { 1083 dhcpmsg(MSG_DEBUG, 1084 "dhcp_assemble_fqdn: no domain name for %s", 1085 dsmp->dsm_name); 1086 return (B_FALSE); 1087 } 1088 1089 /* 1090 * Finish constructing FQDN. Account for space needed to hold a 1091 * separator '.' and a terminating '.'. 1092 */ 1093 len = strlen(domainname); 1094 needdots = 1 + (domainname[len - 1] != '.'); 1095 1096 if (pos + len + needdots >= buflen) { 1097 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long" 1098 " FQDN %s.%s for %s", fqdnbuf, domainname, 1099 dsmp->dsm_name); 1100 return (B_FALSE); 1101 } 1102 1103 /* add separator and then domain name */ 1104 fqdnbuf[pos++] = '.'; 1105 if (strlcpy(fqdnbuf + pos, domainname, buflen - pos) >= 1106 buflen - pos) { 1107 /* shouldn't get here as we checked above */ 1108 return (B_FALSE); 1109 } 1110 pos += len; 1111 1112 /* ensure the final character is '.' */ 1113 if (needdots > 1) 1114 fqdnbuf[pos++] = '.'; /* following is already zeroed */ 1115 } 1116 1117 if (!ipadm_is_valid_hostname(fqdnbuf)) { 1118 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: invalid FQDN %s" 1119 " for %s", fqdnbuf, dsmp->dsm_name); 1120 return (B_FALSE); 1121 } 1122 1123 return (B_TRUE); 1124 } 1125 1126 /* 1127 * is_fqdn() : Determine if the `hostname' can be considered as a Fully 1128 * Qualified Domain Name by being "rooted" (i.e., ending in '.') 1129 * or by containing at least three DNS labels (e.g., 1130 * srv.example.com). 1131 * 1132 * input: const char *: the hostname to inspect; 1133 * output: boolean_t: B_TRUE if `hostname' is not NULL satisfies the 1134 * criteria above; otherwise, B_FALSE; 1135 */ 1136 1137 boolean_t 1138 is_fqdn(const char *hostname) 1139 { 1140 const char *c; 1141 size_t i; 1142 1143 if (hostname == NULL) 1144 return (B_FALSE); 1145 1146 i = strlen(hostname); 1147 if (i > 0 && hostname[i - 1] == '.') 1148 return (B_TRUE); 1149 1150 c = hostname; 1151 i = 0; 1152 while ((c = strchr(c, '.')) != NULL) { 1153 ++i; 1154 ++c; 1155 } 1156 1157 /* at least two separators is inferred to be fully-qualified */ 1158 return (i >= 2); 1159 } 1160 1161 /* 1162 * terminate_at_space(): Reset the first space, 0x20, to 0x0 in the 1163 * specified string. 1164 * 1165 * input: char *: NULL or a null-terminated string; 1166 * output: void. 1167 */ 1168 1169 static void 1170 terminate_at_space(char *value) 1171 { 1172 if (value != NULL) { 1173 char *sp; 1174 1175 sp = strchr(value, ' '); 1176 if (sp != NULL) 1177 *sp = '\0'; 1178 } 1179 } 1180 1181 /* 1182 * get_offered_domainname_v4(): decode a defined v4 DNSdmain value if it 1183 * exists to return a copy of the domain 1184 * name. 1185 * 1186 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from; 1187 * PKT_LIST *: the best packet to be used to construct a REQUEST; 1188 * output: char *: NULL or a copy of the domain name ('\0' terminated); 1189 */ 1190 1191 static char * 1192 get_offered_domainname_v4(PKT_LIST *offer) 1193 { 1194 char *domainname = NULL; 1195 DHCP_OPT *opt; 1196 1197 if ((opt = offer->opts[CD_DNSDOMAIN]) != NULL) { 1198 uchar_t *valptr; 1199 dhcp_symbol_t *symp; 1200 1201 valptr = (uchar_t *)opt + DHCP_OPT_META_LEN; 1202 1203 symp = inittab_getbycode( 1204 ITAB_CAT_STANDARD, ITAB_CONS_INFO, opt->code); 1205 if (symp != NULL) { 1206 domainname = inittab_decode(symp, valptr, 1207 opt->len, B_TRUE); 1208 terminate_at_space(domainname); 1209 free(symp); 1210 } 1211 } 1212 1213 return (domainname); 1214 } 1215 1216 /* 1217 * save_domainname(): assign dsm_dhcp_domainname from 1218 * get_offered_domainname_v4 or leave the field NULL if no 1219 * option is present. 1220 * 1221 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from; 1222 * PKT_LIST *: the best packet to be used to construct a REQUEST; 1223 * output: void 1224 */ 1225 1226 void 1227 save_domainname(dhcp_smach_t *dsmp, PKT_LIST *offer) 1228 { 1229 char *domainname = NULL; 1230 1231 free(dsmp->dsm_dhcp_domainname); 1232 dsmp->dsm_dhcp_domainname = NULL; 1233 1234 if (!dsmp->dsm_isv6) { 1235 domainname = get_offered_domainname_v4(offer); 1236 } 1237 1238 dsmp->dsm_dhcp_domainname = domainname; 1239 } 1240