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