7388 Support -h <hostname> for ipadm DHCP
[unleashed.git] / usr / src / cmd / cmd-inet / sbin / dhcpagent / util.c
blob6b69dfffe7c671328fd30650cbc0d81dc3269420
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>.
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>
53 #include "states.h"
54 #include "agent.h"
55 #include "interface.h"
56 #include "util.h"
57 #include "packet.h"
58 #include "defaults.h"
61 * this file contains utility functions that have no real better home
62 * of their own. they can largely be broken into six categories:
64 * o conversion functions -- functions to turn integers into strings,
65 * or to convert between units of a similar measure.
67 * o time and timer functions -- functions to handle time measurement
68 * and events.
70 * o ipc-related functions -- functions to simplify the generation of
71 * ipc messages to the agent's clients.
73 * o signal-related functions -- functions to clean up the agent when
74 * it receives a signal.
76 * o routing table manipulation functions
78 * o true miscellany -- anything else
81 #define ETCNODENAME "/etc/nodename"
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);
88 * pkt_type_to_string(): stringifies a packet type
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
95 const char *
96 pkt_type_to_string(uchar_t type, boolean_t isv6)
99 * note: the ordering in these arrays allows direct indexing of the
100 * table based on the RFC packet type value passed in.
103 static const char *v4types[] = {
104 "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE",
105 "ACK", "NAK", "RELEASE", "INFORM"
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"
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]);
130 * monosec_to_string(): converts a monosec_t into a date string
132 * input: monosec_t: the monosec_t to convert
133 * output: const char *: the corresponding date string
136 const char *
137 monosec_to_string(monosec_t monosec)
139 time_t time = monosec_to_time(monosec);
140 char *time_string = ctime(&time);
142 /* strip off the newline -- ugh, why, why, why.. */
143 time_string[strlen(time_string) - 1] = '\0';
144 return (time_string);
148 * monosec(): returns a monotonically increasing time in seconds that
149 * is not affected by stime(2) or adjtime(2).
151 * input: void
152 * output: monosec_t: the number of seconds since some time in the past
155 monosec_t
156 monosec(void)
158 return (gethrtime() / NANOSEC);
162 * monosec_to_time(): converts a monosec_t into real wall time
164 * input: monosec_t: the absolute monosec_t to convert
165 * output: time_t: the absolute time that monosec_t represents in wall time
168 time_t
169 monosec_to_time(monosec_t abs_monosec)
171 return (abs_monosec - monosec()) + time(NULL);
175 * hrtime_to_monosec(): converts a hrtime_t to monosec_t
177 * input: hrtime_t: the time to convert
178 * output: monosec_t: the time in monosec_t
181 monosec_t
182 hrtime_to_monosec(hrtime_t hrtime)
184 return (hrtime / NANOSEC);
188 * print_server_msg(): prints a message from a DHCP server
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
196 void
197 print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen)
199 if (msglen > 0) {
200 dhcpmsg(MSG_INFO, "%s: message from server: %.*s",
201 dsmp->dsm_name, msglen, msg);
206 * alrm_exit(): Signal handler for SIGARLM. terminates grandparent.
208 * input: int: signal the handler was called with.
210 * output: void
213 static void
214 alrm_exit(int sig)
216 int exitval;
218 if (sig == SIGALRM && grandparent != 0)
219 exitval = EXIT_SUCCESS;
220 else
221 exitval = EXIT_FAILURE;
223 _exit(exitval);
227 * daemonize(): daemonizes the process
229 * input: void
230 * output: int: 1 on success, 0 on failure
234 daemonize(void)
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.
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.
250 * grandparent is only set for the adopt case; other
251 * cases do not require the wait.
254 if (grandparent != 0)
255 (void) signal(SIGALRM, alrm_exit);
257 switch (fork()) {
259 case -1:
260 return (0);
262 case 0:
263 if (grandparent != 0)
264 (void) signal(SIGALRM, SIG_DFL);
267 * setsid() makes us lose our controlling terminal,
268 * and become both a session leader and a process
269 * group leader.
272 (void) setsid();
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.
281 switch (fork()) {
283 case -1:
284 return (0);
286 case 0:
287 (void) signal(SIGHUP, SIG_IGN);
288 (void) chdir("/");
289 (void) umask(022);
290 closefrom(0);
291 break;
293 default:
294 _exit(EXIT_SUCCESS);
296 break;
298 default:
299 if (grandparent != 0) {
300 (void) signal(SIGCHLD, SIG_IGN);
302 * Note that we're not the agent here, so the DHCP
303 * logging subsystem hasn't been configured yet.
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.");
312 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
313 "wait finished");
315 _exit(EXIT_SUCCESS);
318 return (1);
322 * update_default_route(): update the interface's default route
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
331 static boolean_t
332 update_default_route(uint32_t ifindex, int type, struct in_addr *gateway_nbo,
333 int flags)
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;
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;
351 rtmsg.rm_gw.sin_family = AF_INET;
352 rtmsg.rm_gw.sin_addr = *gateway_nbo;
354 rtmsg.rm_dst.sin_family = AF_INET;
355 rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY);
357 rtmsg.rm_mask.sin_family = AF_INET;
358 rtmsg.rm_mask.sin_addr.s_addr = htonl(0);
360 rtmsg.rm_ifp.sdl_family = AF_LINK;
361 rtmsg.rm_ifp.sdl_index = ifindex;
363 return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg));
367 * add_default_route(): add the default route to the given gateway
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
374 boolean_t
375 add_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
377 return (update_default_route(ifindex, RTM_ADD, gateway_nbo, RTF_UP));
381 * del_default_route(): deletes the default route to the given gateway
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
388 boolean_t
389 del_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
391 if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */
392 return (B_TRUE);
394 return (update_default_route(ifindex, RTM_DELETE, gateway_nbo, 0));
398 * inactivity_shutdown(): shuts down agent if there are no state machines left
399 * to manage
401 * input: iu_tq_t *: unused
402 * void *: unused
403 * output: void
406 /* ARGSUSED */
407 void
408 inactivity_shutdown(iu_tq_t *tqp, void *arg)
410 if (smach_count() > 0) /* shouldn't happen, but... */
411 return;
413 dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out");
415 iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL);
419 * graceful_shutdown(): shuts down the agent gracefully
421 * input: int: the signal that caused graceful_shutdown to be called
422 * output: void
425 void
426 graceful_shutdown(int sig)
428 iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE :
429 DHCP_REASON_SIGNAL), drain_script, NULL);
433 * bind_sock(): binds a socket to a given IP address and port number
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
441 boolean_t
442 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo)
444 struct sockaddr_in sin;
445 int on = 1;
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);
452 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
454 return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0);
458 * bind_sock_v6(): binds a socket to a given IP address and port number
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
466 boolean_t
467 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo)
469 struct sockaddr_in6 sin6;
470 int on = 1;
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));
480 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
482 return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
486 * iffile_to_hostname(): return the hostname contained on a line of the form
488 * [ ^I]*inet[ ^I]+hostname[\n]*\0
490 * in the file located at the specified path
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
496 #define IFLINE_MAX 1024 /* maximum length of a hostname.<if> line */
498 const char *
499 iffile_to_hostname(const char *path)
501 FILE *fp;
502 static char ifline[IFLINE_MAX];
504 fp = fopen(path, "r");
505 if (fp == NULL)
506 return (NULL);
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
514 * [ ^I]*inet[ ^I]+hostname[\n]*\0
516 * extract the host name from it, and check it for validity.
518 while (fgets(ifline, sizeof (ifline), fp) != NULL) {
519 char *p;
521 if ((p = strstr(ifline, "inet")) != NULL) {
522 if ((p != ifline) && !isspace(p[-1])) {
523 (void) fclose(fp);
524 return (NULL);
526 p += 4; /* skip over "inet" and expect spaces or tabs */
527 if ((*p == '\n') || (*p == '\0')) {
528 (void) fclose(fp);
529 return (NULL);
531 if (isspace(*p)) {
532 char *nlptr;
534 /* no need to read more of the file */
535 (void) fclose(fp);
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);
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);
555 } else {
556 (void) fclose(fp);
557 return (NULL);
562 (void) fclose(fp);
563 return (NULL);
567 * init_timer(): set up a DHCP timer
569 * input: dhcp_timer_t *: the timer to set up
570 * output: void
573 void
574 init_timer(dhcp_timer_t *dt, lease_t startval)
576 dt->dt_id = -1;
577 dt->dt_start = startval;
581 * cancel_timer(): cancel a DHCP timer
583 * input: dhcp_timer_t *: the timer to cancel
584 * output: boolean_t: B_TRUE on success, B_FALSE otherwise
587 boolean_t
588 cancel_timer(dhcp_timer_t *dt)
590 if (dt->dt_id == -1)
591 return (B_TRUE);
593 if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) {
594 dt->dt_id = -1;
595 return (B_TRUE);
598 return (B_FALSE);
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.
606 * input: dhcp_timer_t *: the timer to schedule
607 * output: boolean_t: B_TRUE on success, B_FALSE otherwise
610 boolean_t
611 schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg)
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);
620 * dhcpv6_status_code(): report on a DHCPv6 status code found in an option
621 * buffer.
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
632 dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr,
633 const char **msg, uint_t *msglenp)
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"
645 static char sbuf[32];
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);
658 *msg = (const char *)(d6o + 1) + 2;
659 *msglenp = olen - 2;
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];
672 return (status);
675 void
676 write_lease_to_hostconf(dhcp_smach_t *dsmp)
678 PKT_LIST *plp[2];
679 const char *hcfile;
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);
698 * Try to get a string from the first line of a file, up to but not
699 * including any space (0x20) or newline.
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.
708 static boolean_t
709 dhcp_get_oneline(const char *filename, char *buf, size_t buflen)
711 char value[SYS_NMLN], *c;
712 int fd, i;
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';
730 if (strlcpy(buf, value, buflen) >= buflen) {
731 dhcpmsg(MSG_WARNING, "dhcp_get_oneline: too"
732 " long value, %s", value);
733 *buf = '\0';
736 (void) close(fd);
739 return (*buf != '\0');
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.
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.
755 static boolean_t
756 dhcp_get_nodename(char *buf, size_t buflen)
758 return (dhcp_get_oneline(ETCNODENAME, buf, buflen));
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(4) is defined.
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.
773 boolean_t
774 dhcp_add_hostname_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
776 const char *reqhost;
777 char nodename[MAXNAMELEN];
779 if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_HOSTNAME))
780 return (B_FALSE);
782 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: DF_REQUEST_HOSTNAME");
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];
790 (void) snprintf(hostfile, sizeof (hostfile),
791 "/etc/hostname.%s", dsmp->dsm_name);
792 reqhost = iffile_to_hostname(hostfile);
795 if (reqhost == NULL && (dsmp->dsm_dflags & DHCP_IF_PRIMARY) &&
796 dhcp_get_nodename(nodename, sizeof (nodename))) {
797 reqhost = nodename;
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");
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);
818 return (B_TRUE);
822 * dhcp_add_fqdn_opt(): Set client FQDN option if dhcp_assemble_fqdn()
823 * initializes an FQDN, or else do nothing.
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.
830 boolean_t
831 dhcp_add_fqdn_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
834 * RFC 4702 section 2:
836 * The format of the Client FQDN option is:
838 * Code Len Flags RCODE1 RCODE2 Domain Name
839 * +------+------+------+------+------+------+--
840 * | 81 | n | | | | ...
841 * +------+------+------+------+------+------+--
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.
847 * The format of the Flags field is:
849 * 0 1 2 3 4 5 6 7
850 * +-+-+-+-+-+-+-+-+
851 * | MBZ |N|E|O|S|
852 * +-+-+-+-+-+-+-+-+
854 * where MBZ is ignored and NEOS are:
856 * S = 1 to request that "the server SHOULD perform the A RR (FQDN-to-
857 * address) DNS updates;
859 * O = 0, for a server-only response bit;
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 ....";
865 * N = 0 to request that "the server SHALL perform DNS updates [of the
866 * PTR RR]." (1 would request SHALL NOT update).
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;
880 if (dsmp->dsm_isv6)
881 return (B_FALSE);
883 if (!dhcp_assemble_fqdn(fqdnbuf, sizeof (fqdnbuf), dsmp))
884 return (B_FALSE);
886 /* encode the FQDN in canonical wire format */
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);
895 dhcpmsg(MSG_DEBUG, "dhcp_add_fqdn_opt: interface FQDN is %s"
896 " for %s", fqdnbuf, dsmp->dsm_name);
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);
905 return (B_TRUE);
909 * dhcp_adopt_domainname(): Set namebuf if either dsm_dhcp_domainname or
910 * resolv's "default domain (deprecated)" is defined.
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.
919 static boolean_t
920 dhcp_adopt_domainname(char *namebuf, size_t buflen, dhcp_smach_t *dsmp)
922 const char *domainname;
923 struct __res_state res_state;
924 int lasterrno;
926 domainname = dsmp->dsm_dhcp_domainname;
928 if (ipadm_is_nil_hostname(domainname)) {
930 * fall back to resolv's "default domain (deprecated)"
932 bzero(&res_state, sizeof (struct __res_state));
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);
940 domainname = NULL;
941 if (!ipadm_is_nil_hostname(res_state.defdname))
942 domainname = res_state.defdname;
944 /* N.b. res_state.defdname survives the following call */
945 res_ndestroy(&res_state);
948 if (domainname == NULL)
949 return (B_FALSE);
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);
958 return (B_TRUE);
962 * dhcp_pick_domainname(): Set namebuf if DNS_DOMAINNAME is defined in
963 * /etc/default/dhcpagent or if dhcp_adopt_domainname()
964 * succeeds.
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.
973 static boolean_t
974 dhcp_pick_domainname(char *namebuf, size_t buflen, dhcp_smach_t *dsmp)
976 const char *domainname;
979 * Try to use a static DNS_DOMAINNAME if defined in
980 * /etc/default/dhcpagent.
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);
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);
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(1M) -h,--reqhost) or the interface is
1004 * primary and a nodename(4) 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.
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.
1015 static boolean_t
1016 dhcp_assemble_fqdn(char *fqdnbuf, size_t buflen, dhcp_smach_t *dsmp)
1018 char nodename[MAXNAMELEN], *reqhost;
1019 size_t pos, len;
1022 if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_FQDN))
1023 return (B_FALSE);
1025 dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: DF_REQUEST_FQDN");
1027 /* It's convenient to ensure fqdnbuf is always null-terminated */
1028 bzero(fqdnbuf, buflen);
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;
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);
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);
1051 * If not yet FQDN, construct if possible
1053 if (!is_fqdn(reqhost)) {
1054 char domainname[MAXNAMELEN];
1055 size_t needdots;
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);
1066 * Finish constructing FQDN. Account for space needed to hold a
1067 * separator '.' and a terminating '.'.
1069 len = strlen(domainname);
1070 needdots = 1 + (domainname[len - 1] != '.');
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);
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);
1086 pos += len;
1088 /* ensure the final character is '.' */
1089 if (needdots > 1)
1090 fqdnbuf[pos++] = '.'; /* following is already zeroed */
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);
1099 return (B_TRUE);
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).
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;
1113 boolean_t
1114 is_fqdn(const char *hostname)
1116 const char *c;
1117 size_t i;
1119 if (hostname == NULL)
1120 return (B_FALSE);
1122 i = strlen(hostname);
1123 if (i > 0 && hostname[i - 1] == '.')
1124 return (B_TRUE);
1126 c = hostname;
1127 i = 0;
1128 while ((c = strchr(c, '.')) != NULL) {
1129 ++i;
1130 ++c;
1133 /* at least two separators is inferred to be fully-qualified */
1134 return (i >= 2);
1138 * terminate_at_space(): Reset the first space, 0x20, to 0x0 in the
1139 * specified string.
1141 * input: char *: NULL or a null-terminated string;
1142 * output: void.
1145 static void
1146 terminate_at_space(char *value)
1148 if (value != NULL) {
1149 char *sp;
1151 sp = strchr(value, ' ');
1152 if (sp != NULL)
1153 *sp = '\0';
1158 * get_offered_domainname_v4(): decode a defined v4 DNSdmain value if it
1159 * exists to return a copy of the domain
1160 * name.
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);
1167 static char *
1168 get_offered_domainname_v4(PKT_LIST *offer)
1170 char *domainname = NULL;
1171 DHCP_OPT *opt;
1173 if ((opt = offer->opts[CD_DNSDOMAIN]) != NULL) {
1174 uchar_t *valptr;
1175 dhcp_symbol_t *symp;
1177 valptr = (uchar_t *)opt + DHCP_OPT_META_LEN;
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);
1189 return (domainname);
1193 * save_domainname(): assign dsm_dhcp_domainname from
1194 * get_offered_domainname_v4 or leave the field NULL if no
1195 * option is present.
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
1202 void
1203 save_domainname(dhcp_smach_t *dsmp, PKT_LIST *offer)
1205 char *domainname = NULL;
1207 free(dsmp->dsm_dhcp_domainname);
1208 dsmp->dsm_dhcp_domainname = NULL;
1210 if (!dsmp->dsm_isv6) {
1211 domainname = get_offered_domainname_v4(offer);
1214 dsmp->dsm_dhcp_domainname = domainname;