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