xref: /illumos-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c (revision 628e3cbed6489fa1db545d8524a06cd6535af456)
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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <unistd.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <stdlib.h>
32 #include <netinet/in.h>		/* struct in_addr */
33 #include <netinet/dhcp.h>
34 #include <signal.h>
35 #include <sys/socket.h>
36 #include <net/route.h>
37 #include <net/if_arp.h>
38 #include <string.h>
39 #include <dhcpmsg.h>
40 #include <ctype.h>
41 #include <netdb.h>
42 #include <fcntl.h>
43 #include <stdio.h>
44 
45 #include "states.h"
46 #include "agent.h"
47 #include "interface.h"
48 #include "util.h"
49 #include "packet.h"
50 
51 /*
52  * this file contains utility functions that have no real better home
53  * of their own.  they can largely be broken into six categories:
54  *
55  *  o  conversion functions -- functions to turn integers into strings,
56  *     or to convert between units of a similar measure.
57  *
58  *  o  time and timer functions -- functions to handle time measurement
59  *     and events.
60  *
61  *  o  ipc-related functions -- functions to simplify the generation of
62  *     ipc messages to the agent's clients.
63  *
64  *  o  signal-related functions -- functions to clean up the agent when
65  *     it receives a signal.
66  *
67  *  o  routing table manipulation functions
68  *
69  *  o  true miscellany -- anything else
70  */
71 
72 /*
73  * pkt_type_to_string(): stringifies a packet type
74  *
75  *   input: uchar_t: a DHCP packet type value, RFC 2131 or 3315
76  *	    boolean_t: B_TRUE if IPv6
77  *  output: const char *: the stringified packet type
78  */
79 
80 const char *
81 pkt_type_to_string(uchar_t type, boolean_t isv6)
82 {
83 	/*
84 	 * note: the ordering in these arrays allows direct indexing of the
85 	 *	 table based on the RFC packet type value passed in.
86 	 */
87 
88 	static const char *v4types[] = {
89 		"BOOTP",  "DISCOVER", "OFFER",   "REQUEST", "DECLINE",
90 		"ACK",    "NAK",      "RELEASE", "INFORM"
91 	};
92 	static const char *v6types[] = {
93 		NULL, "SOLICIT", "ADVERTISE", "REQUEST",
94 		"CONFIRM", "RENEW", "REBIND", "REPLY",
95 		"RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST",
96 		"RELAY-FORW", "RELAY-REPL"
97 	};
98 
99 	if (isv6) {
100 		if (type >= sizeof (v6types) / sizeof (*v6types) ||
101 		    v6types[type] == NULL)
102 			return ("<unknown>");
103 		else
104 			return (v6types[type]);
105 	} else {
106 		if (type >= sizeof (v4types) / sizeof (*v4types) ||
107 		    v4types[type] == NULL)
108 			return ("<unknown>");
109 		else
110 			return (v4types[type]);
111 	}
112 }
113 
114 /*
115  * monosec_to_string(): converts a monosec_t into a date string
116  *
117  *   input: monosec_t: the monosec_t to convert
118  *  output: const char *: the corresponding date string
119  */
120 
121 const char *
122 monosec_to_string(monosec_t monosec)
123 {
124 	time_t	time = monosec_to_time(monosec);
125 	char	*time_string = ctime(&time);
126 
127 	/* strip off the newline -- ugh, why, why, why.. */
128 	time_string[strlen(time_string) - 1] = '\0';
129 	return (time_string);
130 }
131 
132 /*
133  * monosec(): returns a monotonically increasing time in seconds that
134  *            is not affected by stime(2) or adjtime(2).
135  *
136  *   input: void
137  *  output: monosec_t: the number of seconds since some time in the past
138  */
139 
140 monosec_t
141 monosec(void)
142 {
143 	return (gethrtime() / NANOSEC);
144 }
145 
146 /*
147  * monosec_to_time(): converts a monosec_t into real wall time
148  *
149  *    input: monosec_t: the absolute monosec_t to convert
150  *   output: time_t: the absolute time that monosec_t represents in wall time
151  */
152 
153 time_t
154 monosec_to_time(monosec_t abs_monosec)
155 {
156 	return (abs_monosec - monosec()) + time(NULL);
157 }
158 
159 /*
160  * hrtime_to_monosec(): converts a hrtime_t to monosec_t
161  *
162  *    input: hrtime_t: the time to convert
163  *   output: monosec_t: the time in monosec_t
164  */
165 
166 monosec_t
167 hrtime_to_monosec(hrtime_t hrtime)
168 {
169 	return (hrtime / NANOSEC);
170 }
171 
172 /*
173  * print_server_msg(): prints a message from a DHCP server
174  *
175  *   input: dhcp_smach_t *: the state machine the message is associated with
176  *	    const char *: the string to display
177  *	    uint_t: length of string
178  *  output: void
179  */
180 
181 void
182 print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen)
183 {
184 	if (msglen > 0) {
185 		dhcpmsg(MSG_INFO, "%s: message from server: %.*s",
186 		    dsmp->dsm_name, msglen, msg);
187 	}
188 }
189 
190 /*
191  * alrm_exit(): Signal handler for SIGARLM. terminates grandparent.
192  *
193  *    input: int: signal the handler was called with.
194  *
195  *   output: void
196  */
197 
198 static void
199 alrm_exit(int sig)
200 {
201 	int exitval;
202 
203 	if (sig == SIGALRM && grandparent != 0)
204 		exitval = EXIT_SUCCESS;
205 	else
206 		exitval = EXIT_FAILURE;
207 
208 	_exit(exitval);
209 }
210 
211 /*
212  * daemonize(): daemonizes the process
213  *
214  *   input: void
215  *  output: int: 1 on success, 0 on failure
216  */
217 
218 int
219 daemonize(void)
220 {
221 	/*
222 	 * We've found that adoption takes sufficiently long that
223 	 * a dhcpinfo run after dhcpagent -a is started may occur
224 	 * before the agent is ready to process the request.
225 	 * The result is an error message and an unhappy user.
226 	 *
227 	 * The initial process now sleeps for DHCP_ADOPT_SLEEP,
228 	 * unless interrupted by a SIGALRM, in which case it
229 	 * exits immediately. This has the effect that the
230 	 * grandparent doesn't exit until the dhcpagent is ready
231 	 * to process requests. This defers the the balance of
232 	 * the system start-up script processing until the
233 	 * dhcpagent is ready to field requests.
234 	 *
235 	 * grandparent is only set for the adopt case; other
236 	 * cases do not require the wait.
237 	 */
238 
239 	if (grandparent != 0)
240 		(void) signal(SIGALRM, alrm_exit);
241 
242 	switch (fork()) {
243 
244 	case -1:
245 		return (0);
246 
247 	case  0:
248 		if (grandparent != 0)
249 			(void) signal(SIGALRM, SIG_DFL);
250 
251 		/*
252 		 * setsid() makes us lose our controlling terminal,
253 		 * and become both a session leader and a process
254 		 * group leader.
255 		 */
256 
257 		(void) setsid();
258 
259 		/*
260 		 * under POSIX, a session leader can accidentally
261 		 * (through open(2)) acquire a controlling terminal if
262 		 * it does not have one.  just to be safe, fork again
263 		 * so we are not a session leader.
264 		 */
265 
266 		switch (fork()) {
267 
268 		case -1:
269 			return (0);
270 
271 		case 0:
272 			(void) signal(SIGHUP, SIG_IGN);
273 			(void) chdir("/");
274 			(void) umask(022);
275 			closefrom(0);
276 			break;
277 
278 		default:
279 			_exit(EXIT_SUCCESS);
280 		}
281 		break;
282 
283 	default:
284 		if (grandparent != 0) {
285 			(void) signal(SIGCHLD, SIG_IGN);
286 			/*
287 			 * Note that we're not the agent here, so the DHCP
288 			 * logging subsystem hasn't been configured yet.
289 			 */
290 			syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
291 			    "waiting for adoption to complete.");
292 			if (sleep(DHCP_ADOPT_SLEEP) == 0) {
293 				syslog(LOG_WARNING | LOG_DAEMON,
294 				    "dhcpagent: daemonize: timed out awaiting "
295 				    "adoption.");
296 			}
297 			syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
298 			    "wait finished");
299 		}
300 		_exit(EXIT_SUCCESS);
301 	}
302 
303 	return (1);
304 }
305 
306 /*
307  * update_default_route(): update the interface's default route
308  *
309  *   input: int: the type of message; either RTM_ADD or RTM_DELETE
310  *	    struct in_addr: the default gateway to use
311  *	    const char *: the interface associated with the route
312  *	    int: any additional flags (besides RTF_STATIC and RTF_GATEWAY)
313  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
314  */
315 
316 static boolean_t
317 update_default_route(uint32_t ifindex, int type, struct in_addr *gateway_nbo,
318     int flags)
319 {
320 	struct {
321 		struct rt_msghdr	rm_mh;
322 		struct sockaddr_in	rm_dst;
323 		struct sockaddr_in	rm_gw;
324 		struct sockaddr_in	rm_mask;
325 		struct sockaddr_dl	rm_ifp;
326 	} rtmsg;
327 
328 	(void) memset(&rtmsg, 0, sizeof (rtmsg));
329 	rtmsg.rm_mh.rtm_version = RTM_VERSION;
330 	rtmsg.rm_mh.rtm_msglen	= sizeof (rtmsg);
331 	rtmsg.rm_mh.rtm_type	= type;
332 	rtmsg.rm_mh.rtm_pid	= getpid();
333 	rtmsg.rm_mh.rtm_flags	= RTF_GATEWAY | RTF_STATIC | flags;
334 	rtmsg.rm_mh.rtm_addrs	= RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP;
335 
336 	rtmsg.rm_gw.sin_family	= AF_INET;
337 	rtmsg.rm_gw.sin_addr	= *gateway_nbo;
338 
339 	rtmsg.rm_dst.sin_family = AF_INET;
340 	rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY);
341 
342 	rtmsg.rm_mask.sin_family = AF_INET;
343 	rtmsg.rm_mask.sin_addr.s_addr = htonl(0);
344 
345 	rtmsg.rm_ifp.sdl_family	= AF_LINK;
346 	rtmsg.rm_ifp.sdl_index	= ifindex;
347 
348 	return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg));
349 }
350 
351 /*
352  * add_default_route(): add the default route to the given gateway
353  *
354  *   input: const char *: the name of the interface associated with the route
355  *	    struct in_addr: the default gateway to add
356  *  output: boolean_t: B_TRUE on success, B_FALSE otherwise
357  */
358 
359 boolean_t
360 add_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
361 {
362 	return (update_default_route(ifindex, RTM_ADD, gateway_nbo, RTF_UP));
363 }
364 
365 /*
366  * del_default_route(): deletes the default route to the given gateway
367  *
368  *   input: const char *: the name of the interface associated with the route
369  *	    struct in_addr: if not INADDR_ANY, the default gateway to remove
370  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
371  */
372 
373 boolean_t
374 del_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
375 {
376 	if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */
377 		return (B_TRUE);
378 
379 	return (update_default_route(ifindex, RTM_DELETE, gateway_nbo, 0));
380 }
381 
382 /*
383  * inactivity_shutdown(): shuts down agent if there are no state machines left
384  *			  to manage
385  *
386  *   input: iu_tq_t *: unused
387  *	    void *: unused
388  *  output: void
389  */
390 
391 /* ARGSUSED */
392 void
393 inactivity_shutdown(iu_tq_t *tqp, void *arg)
394 {
395 	if (smach_count() > 0)	/* shouldn't happen, but... */
396 		return;
397 
398 	dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out");
399 
400 	iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL);
401 }
402 
403 /*
404  * graceful_shutdown(): shuts down the agent gracefully
405  *
406  *   input: int: the signal that caused graceful_shutdown to be called
407  *  output: void
408  */
409 
410 void
411 graceful_shutdown(int sig)
412 {
413 	iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE :
414 	    DHCP_REASON_SIGNAL), drain_script, NULL);
415 }
416 
417 /*
418  * bind_sock(): binds a socket to a given IP address and port number
419  *
420  *   input: int: the socket to bind
421  *	    in_port_t: the port number to bind to, host byte order
422  *	    in_addr_t: the address to bind to, host byte order
423  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
424  */
425 
426 boolean_t
427 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo)
428 {
429 	struct sockaddr_in	sin;
430 	int			on = 1;
431 
432 	(void) memset(&sin, 0, sizeof (struct sockaddr_in));
433 	sin.sin_family = AF_INET;
434 	sin.sin_port   = htons(port_hbo);
435 	sin.sin_addr.s_addr = htonl(addr_hbo);
436 
437 	(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
438 
439 	return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0);
440 }
441 
442 /*
443  * bind_sock_v6(): binds a socket to a given IP address and port number
444  *
445  *   input: int: the socket to bind
446  *	    in_port_t: the port number to bind to, host byte order
447  *	    in6_addr_t: the address to bind to, network byte order
448  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
449  */
450 
451 boolean_t
452 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo)
453 {
454 	struct sockaddr_in6	sin6;
455 	int			on = 1;
456 
457 	(void) memset(&sin6, 0, sizeof (struct sockaddr_in6));
458 	sin6.sin6_family = AF_INET6;
459 	sin6.sin6_port   = htons(port_hbo);
460 	if (addr_nbo != NULL) {
461 		(void) memcpy(&sin6.sin6_addr, addr_nbo,
462 		    sizeof (sin6.sin6_addr));
463 	}
464 
465 	(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
466 
467 	return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
468 }
469 
470 /*
471  * valid_hostname(): check whether a string is a valid hostname
472  *
473  *   input: const char *: the string to verify as a hostname
474  *  output: boolean_t: B_TRUE if the string is a valid hostname
475  *
476  * Note that we accept both host names beginning with a digit and
477  * those containing hyphens.  Neither is strictly legal according
478  * to the RFCs, but both are in common practice, so we endeavour
479  * to not break what customers are using.
480  */
481 
482 static boolean_t
483 valid_hostname(const char *hostname)
484 {
485 	unsigned int i;
486 
487 	for (i = 0; hostname[i] != '\0'; i++) {
488 
489 		if (isalpha(hostname[i]) || isdigit(hostname[i]) ||
490 		    (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0)))
491 			continue;
492 
493 		return (B_FALSE);
494 	}
495 
496 	return (i > 0);
497 }
498 
499 /*
500  * iffile_to_hostname(): return the hostname contained on a line of the form
501  *
502  * [ ^I]*inet[ ^I]+hostname[\n]*\0
503  *
504  * in the file located at the specified path
505  *
506  *   input: const char *: the path of the file to look in for the hostname
507  *  output: const char *: the hostname at that path, or NULL on failure
508  */
509 
510 #define	IFLINE_MAX	1024	/* maximum length of a hostname.<if> line */
511 
512 const char *
513 iffile_to_hostname(const char *path)
514 {
515 	FILE		*fp;
516 	static char	ifline[IFLINE_MAX];
517 
518 	fp = fopen(path, "r");
519 	if (fp == NULL)
520 		return (NULL);
521 
522 	/*
523 	 * /etc/hostname.<if> may contain multiple ifconfig commands, but each
524 	 * such command is on a separate line (see the "while read ifcmds" code
525 	 * in /etc/init.d/inetinit).  Thus we will read the file a line at a
526 	 * time, searching for a line of the form
527 	 *
528 	 * [ ^I]*inet[ ^I]+hostname[\n]*\0
529 	 *
530 	 * extract the host name from it, and check it for validity.
531 	 */
532 	while (fgets(ifline, sizeof (ifline), fp) != NULL) {
533 		char *p;
534 
535 		if ((p = strstr(ifline, "inet")) != NULL) {
536 			if ((p != ifline) && !isspace(p[-1])) {
537 				(void) fclose(fp);
538 				return (NULL);
539 			}
540 			p += 4;	/* skip over "inet" and expect spaces or tabs */
541 			if ((*p == '\n') || (*p == '\0')) {
542 				(void) fclose(fp);
543 				return (NULL);
544 			}
545 			if (isspace(*p)) {
546 				char *nlptr;
547 
548 				/* no need to read more of the file */
549 				(void) fclose(fp);
550 
551 				while (isspace(*p))
552 					p++;
553 				if ((nlptr = strrchr(p, '\n')) != NULL)
554 					*nlptr = '\0';
555 				if (strlen(p) > MAXHOSTNAMELEN) {
556 					dhcpmsg(MSG_WARNING,
557 					    "iffile_to_hostname:"
558 					    " host name too long");
559 					return (NULL);
560 				}
561 				if (valid_hostname(p)) {
562 					return (p);
563 				} else {
564 					dhcpmsg(MSG_WARNING,
565 					    "iffile_to_hostname:"
566 					    " host name not valid");
567 					return (NULL);
568 				}
569 			} else {
570 				(void) fclose(fp);
571 				return (NULL);
572 			}
573 		}
574 	}
575 
576 	(void) fclose(fp);
577 	return (NULL);
578 }
579 
580 /*
581  * init_timer(): set up a DHCP timer
582  *
583  *   input: dhcp_timer_t *: the timer to set up
584  *  output: void
585  */
586 
587 void
588 init_timer(dhcp_timer_t *dt, lease_t startval)
589 {
590 	dt->dt_id = -1;
591 	dt->dt_start = startval;
592 }
593 
594 /*
595  * cancel_timer(): cancel a DHCP timer
596  *
597  *   input: dhcp_timer_t *: the timer to cancel
598  *  output: boolean_t: B_TRUE on success, B_FALSE otherwise
599  */
600 
601 boolean_t
602 cancel_timer(dhcp_timer_t *dt)
603 {
604 	if (dt->dt_id == -1)
605 		return (B_TRUE);
606 
607 	if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) {
608 		dt->dt_id = -1;
609 		return (B_TRUE);
610 	}
611 
612 	return (B_FALSE);
613 }
614 
615 /*
616  * schedule_timer(): schedule a DHCP timer.  Note that it must not be already
617  *		     running, and that we can't cancel here.  If it were, and
618  *		     we did, we'd leak a reference to the callback argument.
619  *
620  *   input: dhcp_timer_t *: the timer to schedule
621  *  output: boolean_t: B_TRUE on success, B_FALSE otherwise
622  */
623 
624 boolean_t
625 schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg)
626 {
627 	if (dt->dt_id != -1)
628 		return (B_FALSE);
629 	dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg);
630 	return (dt->dt_id != -1);
631 }
632 
633 /*
634  * dhcpv6_status_code(): report on a DHCPv6 status code found in an option
635  *			 buffer.
636  *
637  *   input: const dhcpv6_option_t *: pointer to option
638  *	    uint_t: option length
639  *	    const char **: error string (nul-terminated)
640  *	    const char **: message from server (unterminated)
641  *	    uint_t *: length of server message
642  *  output: int: -1 on error, or >= 0 for a DHCPv6 status code
643  */
644 
645 int
646 dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr,
647     const char **msg, uint_t *msglenp)
648 {
649 	uint16_t status;
650 	static const char *v6_status[] = {
651 		NULL,
652 		"Unknown reason",
653 		"Server has no addresses available",
654 		"Client record unavailable",
655 		"Prefix inappropriate for link",
656 		"Client must use multicast",
657 		"No prefix available"
658 	};
659 	static char sbuf[32];
660 
661 	*estr = "";
662 	*msg = "";
663 	*msglenp = 0;
664 	if (d6o == NULL)
665 		return (0);
666 	olen -= sizeof (*d6o);
667 	if (olen < 2) {
668 		*estr = "garbled status code";
669 		return (-1);
670 	}
671 
672 	*msg = (const char *)(d6o + 1) + 2;
673 	*msglenp = olen - 2;
674 
675 	(void) memcpy(&status, d6o + 1, sizeof (status));
676 	status = ntohs(status);
677 	if (status > 0) {
678 		if (status > DHCPV6_STAT_NOPREFIX) {
679 			(void) snprintf(sbuf, sizeof (sbuf), "status %u",
680 			    status);
681 			*estr = sbuf;
682 		} else {
683 			*estr = v6_status[status];
684 		}
685 	}
686 	return (status);
687 }
688