xref: /titanic_41/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c (revision 3125ebfc35130d243e775dc38a6a59be4df0b137)
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(const char *ifname, 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	= if_nametoindex(ifname);
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(const char *ifname, struct in_addr *gateway_nbo)
361 {
362 	if (strchr(ifname, ':') != NULL)	/* see README */
363 		return (B_TRUE);
364 
365 	return (update_default_route(ifname, RTM_ADD, gateway_nbo, RTF_UP));
366 }
367 
368 /*
369  * del_default_route(): deletes the default route to the given gateway
370  *
371  *   input: const char *: the name of the interface associated with the route
372  *	    struct in_addr: if not INADDR_ANY, the default gateway to remove
373  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
374  */
375 
376 boolean_t
377 del_default_route(const char *ifname, struct in_addr *gateway_nbo)
378 {
379 	if (strchr(ifname, ':') != NULL)
380 		return (B_TRUE);
381 
382 	if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */
383 		return (B_TRUE);
384 
385 	return (update_default_route(ifname, RTM_DELETE, gateway_nbo, 0));
386 }
387 
388 /*
389  * inactivity_shutdown(): shuts down agent if there are no state machines left
390  *			  to manage
391  *
392  *   input: iu_tq_t *: unused
393  *	    void *: unused
394  *  output: void
395  */
396 
397 /* ARGSUSED */
398 void
399 inactivity_shutdown(iu_tq_t *tqp, void *arg)
400 {
401 	if (smach_count() > 0)	/* shouldn't happen, but... */
402 		return;
403 
404 	dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out");
405 
406 	iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL);
407 }
408 
409 /*
410  * graceful_shutdown(): shuts down the agent gracefully
411  *
412  *   input: int: the signal that caused graceful_shutdown to be called
413  *  output: void
414  */
415 
416 void
417 graceful_shutdown(int sig)
418 {
419 	iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE :
420 	    DHCP_REASON_SIGNAL), drain_script, NULL);
421 }
422 
423 /*
424  * bind_sock(): binds a socket to a given IP address and port number
425  *
426  *   input: int: the socket to bind
427  *	    in_port_t: the port number to bind to, host byte order
428  *	    in_addr_t: the address to bind to, host byte order
429  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
430  */
431 
432 boolean_t
433 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo)
434 {
435 	struct sockaddr_in	sin;
436 	int			on = 1;
437 
438 	(void) memset(&sin, 0, sizeof (struct sockaddr_in));
439 	sin.sin_family = AF_INET;
440 	sin.sin_port   = htons(port_hbo);
441 	sin.sin_addr.s_addr = htonl(addr_hbo);
442 
443 	(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
444 
445 	return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0);
446 }
447 
448 /*
449  * bind_sock_v6(): binds a socket to a given IP address and port number
450  *
451  *   input: int: the socket to bind
452  *	    in_port_t: the port number to bind to, host byte order
453  *	    in6_addr_t: the address to bind to, network byte order
454  *  output: boolean_t: B_TRUE on success, B_FALSE on failure
455  */
456 
457 boolean_t
458 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo)
459 {
460 	struct sockaddr_in6	sin6;
461 	int			on = 1;
462 
463 	(void) memset(&sin6, 0, sizeof (struct sockaddr_in6));
464 	sin6.sin6_family = AF_INET6;
465 	sin6.sin6_port   = htons(port_hbo);
466 	if (addr_nbo != NULL) {
467 		(void) memcpy(&sin6.sin6_addr, addr_nbo,
468 		    sizeof (sin6.sin6_addr));
469 	}
470 
471 	(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
472 
473 	return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
474 }
475 
476 /*
477  * valid_hostname(): check whether a string is a valid hostname
478  *
479  *   input: const char *: the string to verify as a hostname
480  *  output: boolean_t: B_TRUE if the string is a valid hostname
481  *
482  * Note that we accept both host names beginning with a digit and
483  * those containing hyphens.  Neither is strictly legal according
484  * to the RFCs, but both are in common practice, so we endeavour
485  * to not break what customers are using.
486  */
487 
488 static boolean_t
489 valid_hostname(const char *hostname)
490 {
491 	unsigned int i;
492 
493 	for (i = 0; hostname[i] != '\0'; i++) {
494 
495 		if (isalpha(hostname[i]) || isdigit(hostname[i]) ||
496 		    (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0)))
497 			continue;
498 
499 		return (B_FALSE);
500 	}
501 
502 	return (i > 0);
503 }
504 
505 /*
506  * iffile_to_hostname(): return the hostname contained on a line of the form
507  *
508  * [ ^I]*inet[ ^I]+hostname[\n]*\0
509  *
510  * in the file located at the specified path
511  *
512  *   input: const char *: the path of the file to look in for the hostname
513  *  output: const char *: the hostname at that path, or NULL on failure
514  */
515 
516 #define	IFLINE_MAX	1024	/* maximum length of a hostname.<if> line */
517 
518 const char *
519 iffile_to_hostname(const char *path)
520 {
521 	FILE		*fp;
522 	static char	ifline[IFLINE_MAX];
523 
524 	fp = fopen(path, "r");
525 	if (fp == NULL)
526 		return (NULL);
527 
528 	/*
529 	 * /etc/hostname.<if> may contain multiple ifconfig commands, but each
530 	 * such command is on a separate line (see the "while read ifcmds" code
531 	 * in /etc/init.d/inetinit).  Thus we will read the file a line at a
532 	 * time, searching for a line of the form
533 	 *
534 	 * [ ^I]*inet[ ^I]+hostname[\n]*\0
535 	 *
536 	 * extract the host name from it, and check it for validity.
537 	 */
538 	while (fgets(ifline, sizeof (ifline), fp) != NULL) {
539 		char *p;
540 
541 		if ((p = strstr(ifline, "inet")) != NULL) {
542 			if ((p != ifline) && !isspace(p[-1])) {
543 				(void) fclose(fp);
544 				return (NULL);
545 			}
546 			p += 4;	/* skip over "inet" and expect spaces or tabs */
547 			if ((*p == '\n') || (*p == '\0')) {
548 				(void) fclose(fp);
549 				return (NULL);
550 			}
551 			if (isspace(*p)) {
552 				char *nlptr;
553 
554 				/* no need to read more of the file */
555 				(void) fclose(fp);
556 
557 				while (isspace(*p))
558 					p++;
559 				if ((nlptr = strrchr(p, '\n')) != NULL)
560 					*nlptr = '\0';
561 				if (strlen(p) > MAXHOSTNAMELEN) {
562 					dhcpmsg(MSG_WARNING,
563 					    "iffile_to_hostname:"
564 					    " host name too long");
565 					return (NULL);
566 				}
567 				if (valid_hostname(p)) {
568 					return (p);
569 				} else {
570 					dhcpmsg(MSG_WARNING,
571 					    "iffile_to_hostname:"
572 					    " host name not valid");
573 					return (NULL);
574 				}
575 			} else {
576 				(void) fclose(fp);
577 				return (NULL);
578 			}
579 		}
580 	}
581 
582 	(void) fclose(fp);
583 	return (NULL);
584 }
585 
586 /*
587  * init_timer(): set up a DHCP timer
588  *
589  *   input: dhcp_timer_t *: the timer to set up
590  *  output: void
591  */
592 
593 void
594 init_timer(dhcp_timer_t *dt, lease_t startval)
595 {
596 	dt->dt_id = -1;
597 	dt->dt_start = startval;
598 }
599 
600 /*
601  * cancel_timer(): cancel a DHCP timer
602  *
603  *   input: dhcp_timer_t *: the timer to cancel
604  *  output: boolean_t: B_TRUE on success, B_FALSE otherwise
605  */
606 
607 boolean_t
608 cancel_timer(dhcp_timer_t *dt)
609 {
610 	if (dt->dt_id == -1)
611 		return (B_TRUE);
612 
613 	if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) {
614 		dt->dt_id = -1;
615 		return (B_TRUE);
616 	}
617 
618 	return (B_FALSE);
619 }
620 
621 /*
622  * schedule_timer(): schedule a DHCP timer.  Note that it must not be already
623  *		     running, and that we can't cancel here.  If it were, and
624  *		     we did, we'd leak a reference to the callback argument.
625  *
626  *   input: dhcp_timer_t *: the timer to schedule
627  *  output: boolean_t: B_TRUE on success, B_FALSE otherwise
628  */
629 
630 boolean_t
631 schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg)
632 {
633 	if (dt->dt_id != -1)
634 		return (B_FALSE);
635 	dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg);
636 	return (dt->dt_id != -1);
637 }
638 
639 /*
640  * dhcpv6_status_code(): report on a DHCPv6 status code found in an option
641  *			 buffer.
642  *
643  *   input: const dhcpv6_option_t *: pointer to option
644  *	    uint_t: option length
645  *	    const char **: error string (nul-terminated)
646  *	    const char **: message from server (unterminated)
647  *	    uint_t *: length of server message
648  *  output: int: -1 on error, or >= 0 for a DHCPv6 status code
649  */
650 
651 int
652 dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr,
653     const char **msg, uint_t *msglenp)
654 {
655 	uint16_t status;
656 	static const char *v6_status[] = {
657 		NULL,
658 		"Unknown reason",
659 		"Server has no addresses available",
660 		"Client record unavailable",
661 		"Prefix inappropriate for link",
662 		"Client must use multicast",
663 		"No prefix available"
664 	};
665 	static char sbuf[32];
666 
667 	*estr = "";
668 	*msg = "";
669 	*msglenp = 0;
670 	if (d6o == NULL)
671 		return (0);
672 	olen -= sizeof (*d6o);
673 	if (olen < 2) {
674 		*estr = "garbled status code";
675 		return (-1);
676 	}
677 
678 	*msg = (const char *)(d6o + 1) + 2;
679 	*msglenp = olen - 2;
680 
681 	(void) memcpy(&status, d6o + 1, sizeof (status));
682 	status = ntohs(status);
683 	if (status > 0) {
684 		if (status > DHCPV6_STAT_NOPREFIX) {
685 			(void) snprintf(sbuf, sizeof (sbuf), "status %u",
686 			    status);
687 			*estr = sbuf;
688 		} else {
689 			*estr = v6_status[status];
690 		}
691 	}
692 	return (status);
693 }
694