Print this page
7388 Support DHCP Client FQDN. Allow IAID/DUID for all v4.

Split Close
Expand all
Collapse all
          --- old/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c
          +++ new/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c
↓ open down ↓ 12 lines elided ↑ open up ↑
  13   13   * When distributing Covered Code, include this CDDL HEADER in each
  14   14   * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15   15   * If applicable, add the following below this CDDL HEADER, with the
  16   16   * fields enclosed by brackets "[]" replaced with your own identifying
  17   17   * information: Portions Copyright [yyyy] [name of copyright owner]
  18   18   *
  19   19   * CDDL HEADER END
  20   20   */
  21   21  /*
  22   22   * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
       23 + * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
  23   24   */
  24   25  
  25   26  #include <unistd.h>
  26   27  #include <sys/types.h>
  27   28  #include <sys/stat.h>
       29 +#include <sys/utsname.h>
  28   30  #include <stdlib.h>
  29   31  #include <netinet/in.h>         /* struct in_addr */
  30   32  #include <netinet/dhcp.h>
  31   33  #include <signal.h>
  32   34  #include <sys/socket.h>
  33   35  #include <net/route.h>
  34   36  #include <net/if_arp.h>
  35   37  #include <string.h>
  36   38  #include <dhcpmsg.h>
  37   39  #include <ctype.h>
       40 +#include <arpa/inet.h>
       41 +#include <arpa/nameser.h>
       42 +#include <resolv.h>
  38   43  #include <netdb.h>
  39   44  #include <fcntl.h>
  40   45  #include <stdio.h>
  41   46  #include <dhcp_hostconf.h>
       47 +#include <limits.h>
       48 +#include <strings.h>
       49 +#include <libipadm.h>
  42   50  
  43   51  #include "states.h"
  44   52  #include "agent.h"
  45   53  #include "interface.h"
  46   54  #include "util.h"
  47   55  #include "packet.h"
       56 +#include "defaults.h"
  48   57  
  49   58  /*
  50   59   * this file contains utility functions that have no real better home
  51   60   * of their own.  they can largely be broken into six categories:
  52   61   *
  53   62   *  o  conversion functions -- functions to turn integers into strings,
  54   63   *     or to convert between units of a similar measure.
  55   64   *
  56   65   *  o  time and timer functions -- functions to handle time measurement
  57   66   *     and events.
↓ open down ↓ 2 lines elided ↑ open up ↑
  60   69   *     ipc messages to the agent's clients.
  61   70   *
  62   71   *  o  signal-related functions -- functions to clean up the agent when
  63   72   *     it receives a signal.
  64   73   *
  65   74   *  o  routing table manipulation functions
  66   75   *
  67   76   *  o  true miscellany -- anything else
  68   77   */
  69   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 +
  70   85  /*
  71   86   * pkt_type_to_string(): stringifies a packet type
  72   87   *
  73   88   *   input: uchar_t: a DHCP packet type value, RFC 2131 or 3315
  74   89   *          boolean_t: B_TRUE if IPv6
  75   90   *  output: const char *: the stringified packet type
  76   91   */
  77   92  
  78   93  const char *
  79   94  pkt_type_to_string(uchar_t type, boolean_t isv6)
↓ open down ↓ 379 lines elided ↑ open up ↑
 459  474                  (void) memcpy(&sin6.sin6_addr, addr_nbo,
 460  475                      sizeof (sin6.sin6_addr));
 461  476          }
 462  477  
 463  478          (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
 464  479  
 465  480          return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
 466  481  }
 467  482  
 468  483  /*
 469      - * valid_hostname(): check whether a string is a valid hostname
 470      - *
 471      - *   input: const char *: the string to verify as a hostname
 472      - *  output: boolean_t: B_TRUE if the string is a valid hostname
 473      - *
 474      - * Note that we accept both host names beginning with a digit and
 475      - * those containing hyphens.  Neither is strictly legal according
 476      - * to the RFCs, but both are in common practice, so we endeavour
 477      - * to not break what customers are using.
 478      - */
 479      -
 480      -static boolean_t
 481      -valid_hostname(const char *hostname)
 482      -{
 483      -        unsigned int i;
 484      -
 485      -        for (i = 0; hostname[i] != '\0'; i++) {
 486      -
 487      -                if (isalpha(hostname[i]) || isdigit(hostname[i]) ||
 488      -                    (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0)))
 489      -                        continue;
 490      -
 491      -                return (B_FALSE);
 492      -        }
 493      -
 494      -        return (i > 0);
 495      -}
 496      -
 497      -/*
 498  484   * iffile_to_hostname(): return the hostname contained on a line of the form
 499  485   *
 500  486   * [ ^I]*inet[ ^I]+hostname[\n]*\0
 501  487   *
 502  488   * in the file located at the specified path
 503  489   *
 504  490   *   input: const char *: the path of the file to look in for the hostname
 505  491   *  output: const char *: the hostname at that path, or NULL on failure
 506  492   */
 507  493  
↓ open down ↓ 41 lines elided ↑ open up ↑
 549  535                                  while (isspace(*p))
 550  536                                          p++;
 551  537                                  if ((nlptr = strrchr(p, '\n')) != NULL)
 552  538                                          *nlptr = '\0';
 553  539                                  if (strlen(p) > MAXHOSTNAMELEN) {
 554  540                                          dhcpmsg(MSG_WARNING,
 555  541                                              "iffile_to_hostname:"
 556  542                                              " host name too long");
 557  543                                          return (NULL);
 558  544                                  }
 559      -                                if (valid_hostname(p)) {
      545 +                                if (ipadm_is_valid_hostname(p)) {
 560  546                                          return (p);
 561  547                                  } else {
 562  548                                          dhcpmsg(MSG_WARNING,
 563  549                                              "iffile_to_hostname:"
 564  550                                              " host name not valid");
 565  551                                          return (NULL);
 566  552                                  }
 567  553                          } else {
 568  554                                  (void) fclose(fp);
 569  555                                  return (NULL);
↓ open down ↓ 127 lines elided ↑ open up ↑
 697  683              monosec_to_time(dsmp->dsm_curstart_monosec),
 698  684              dsmp->dsm_isv6) != -1) {
 699  685                  dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile);
 700  686          } else if (errno == EROFS) {
 701  687                  dhcpmsg(MSG_DEBUG, "%s is on a read-only file "
 702  688                      "system; not saving lease", hcfile);
 703  689          } else {
 704  690                  dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
 705  691                      "not use cached configuration)", hcfile);
 706  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);
 707 1105  }
    
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX