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