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