/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <netinet/in.h> /* struct in_addr */ #include <netinet/dhcp.h> #include <signal.h> #include <sys/socket.h> #include <net/route.h> #include <net/if_arp.h> #include <string.h> #include <dhcpmsg.h> #include <ctype.h> #include <netdb.h> #include <fcntl.h> #include <stdio.h> #include "states.h" #include "agent.h" #include "interface.h" #include "util.h" #include "packet.h" /* * this file contains utility functions that have no real better home * of their own. they can largely be broken into six categories: * * o conversion functions -- functions to turn integers into strings, * or to convert between units of a similar measure. * * o time and timer functions -- functions to handle time measurement * and events. * * o ipc-related functions -- functions to simplify the generation of * ipc messages to the agent's clients. * * o signal-related functions -- functions to clean up the agent when * it receives a signal. * * o routing table manipulation functions * * o true miscellany -- anything else */ /* * pkt_type_to_string(): stringifies a packet type * * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315 * boolean_t: B_TRUE if IPv6 * output: const char *: the stringified packet type */ const char * pkt_type_to_string(uchar_t type, boolean_t isv6) { /* * note: the ordering in these arrays allows direct indexing of the * table based on the RFC packet type value passed in. */ static const char *v4types[] = { "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE", "ACK", "NAK", "RELEASE", "INFORM" }; static const char *v6types[] = { NULL, "SOLICIT", "ADVERTISE", "REQUEST", "CONFIRM", "RENEW", "REBIND", "REPLY", "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST", "RELAY-FORW", "RELAY-REPL" }; if (isv6) { if (type >= sizeof (v6types) / sizeof (*v6types) || v6types[type] == NULL) return ("<unknown>"); else return (v6types[type]); } else { if (type >= sizeof (v4types) / sizeof (*v4types) || v4types[type] == NULL) return ("<unknown>"); else return (v4types[type]); } } /* * monosec_to_string(): converts a monosec_t into a date string * * input: monosec_t: the monosec_t to convert * output: const char *: the corresponding date string */ const char * monosec_to_string(monosec_t monosec) { time_t time = monosec_to_time(monosec); char *time_string = ctime(&time); /* strip off the newline -- ugh, why, why, why.. */ time_string[strlen(time_string) - 1] = '\0'; return (time_string); } /* * monosec(): returns a monotonically increasing time in seconds that * is not affected by stime(2) or adjtime(2). * * input: void * output: monosec_t: the number of seconds since some time in the past */ monosec_t monosec(void) { return (gethrtime() / NANOSEC); } /* * monosec_to_time(): converts a monosec_t into real wall time * * input: monosec_t: the absolute monosec_t to convert * output: time_t: the absolute time that monosec_t represents in wall time */ time_t monosec_to_time(monosec_t abs_monosec) { return (abs_monosec - monosec()) + time(NULL); } /* * hrtime_to_monosec(): converts a hrtime_t to monosec_t * * input: hrtime_t: the time to convert * output: monosec_t: the time in monosec_t */ monosec_t hrtime_to_monosec(hrtime_t hrtime) { return (hrtime / NANOSEC); } /* * print_server_msg(): prints a message from a DHCP server * * input: dhcp_smach_t *: the state machine the message is associated with * const char *: the string to display * uint_t: length of string * output: void */ void print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen) { if (msglen > 0) { dhcpmsg(MSG_INFO, "%s: message from server: %.*s", dsmp->dsm_name, msglen, msg); } } /* * alrm_exit(): Signal handler for SIGARLM. terminates grandparent. * * input: int: signal the handler was called with. * * output: void */ static void alrm_exit(int sig) { int exitval; if (sig == SIGALRM && grandparent != 0) exitval = EXIT_SUCCESS; else exitval = EXIT_FAILURE; _exit(exitval); } /* * daemonize(): daemonizes the process * * input: void * output: int: 1 on success, 0 on failure */ int daemonize(void) { /* * We've found that adoption takes sufficiently long that * a dhcpinfo run after dhcpagent -a is started may occur * before the agent is ready to process the request. * The result is an error message and an unhappy user. * * The initial process now sleeps for DHCP_ADOPT_SLEEP, * unless interrupted by a SIGALRM, in which case it * exits immediately. This has the effect that the * grandparent doesn't exit until the dhcpagent is ready * to process requests. This defers the the balance of * the system start-up script processing until the * dhcpagent is ready to field requests. * * grandparent is only set for the adopt case; other * cases do not require the wait. */ if (grandparent != 0) (void) signal(SIGALRM, alrm_exit); switch (fork()) { case -1: return (0); case 0: if (grandparent != 0) (void) signal(SIGALRM, SIG_DFL); /* * setsid() makes us lose our controlling terminal, * and become both a session leader and a process * group leader. */ (void) setsid(); /* * under POSIX, a session leader can accidentally * (through open(2)) acquire a controlling terminal if * it does not have one. just to be safe, fork again * so we are not a session leader. */ switch (fork()) { case -1: return (0); case 0: (void) signal(SIGHUP, SIG_IGN); (void) chdir("/"); (void) umask(022); closefrom(0); break; default: _exit(EXIT_SUCCESS); } break; default: if (grandparent != 0) { (void) signal(SIGCHLD, SIG_IGN); /* * Note that we're not the agent here, so the DHCP * logging subsystem hasn't been configured yet. */ syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " "waiting for adoption to complete."); if (sleep(DHCP_ADOPT_SLEEP) == 0) { syslog(LOG_WARNING | LOG_DAEMON, "dhcpagent: daemonize: timed out awaiting " "adoption."); } syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " "wait finished"); } _exit(EXIT_SUCCESS); } return (1); } /* * update_default_route(): update the interface's default route * * input: int: the type of message; either RTM_ADD or RTM_DELETE * struct in_addr: the default gateway to use * const char *: the interface associated with the route * int: any additional flags (besides RTF_STATIC and RTF_GATEWAY) * output: boolean_t: B_TRUE on success, B_FALSE on failure */ static boolean_t update_default_route(const char *ifname, int type, struct in_addr *gateway_nbo, int flags) { struct { struct rt_msghdr rm_mh; struct sockaddr_in rm_dst; struct sockaddr_in rm_gw; struct sockaddr_in rm_mask; struct sockaddr_dl rm_ifp; } rtmsg; (void) memset(&rtmsg, 0, sizeof (rtmsg)); rtmsg.rm_mh.rtm_version = RTM_VERSION; rtmsg.rm_mh.rtm_msglen = sizeof (rtmsg); rtmsg.rm_mh.rtm_type = type; rtmsg.rm_mh.rtm_pid = getpid(); rtmsg.rm_mh.rtm_flags = RTF_GATEWAY | RTF_STATIC | flags; rtmsg.rm_mh.rtm_addrs = RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP; rtmsg.rm_gw.sin_family = AF_INET; rtmsg.rm_gw.sin_addr = *gateway_nbo; rtmsg.rm_dst.sin_family = AF_INET; rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY); rtmsg.rm_mask.sin_family = AF_INET; rtmsg.rm_mask.sin_addr.s_addr = htonl(0); rtmsg.rm_ifp.sdl_family = AF_LINK; rtmsg.rm_ifp.sdl_index = if_nametoindex(ifname); return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg)); } /* * add_default_route(): add the default route to the given gateway * * input: const char *: the name of the interface associated with the route * struct in_addr: the default gateway to add * output: boolean_t: B_TRUE on success, B_FALSE otherwise */ boolean_t add_default_route(const char *ifname, struct in_addr *gateway_nbo) { if (strchr(ifname, ':') != NULL) /* see README */ return (B_TRUE); return (update_default_route(ifname, RTM_ADD, gateway_nbo, RTF_UP)); } /* * del_default_route(): deletes the default route to the given gateway * * input: const char *: the name of the interface associated with the route * struct in_addr: if not INADDR_ANY, the default gateway to remove * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t del_default_route(const char *ifname, struct in_addr *gateway_nbo) { if (strchr(ifname, ':') != NULL) return (B_TRUE); if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */ return (B_TRUE); return (update_default_route(ifname, RTM_DELETE, gateway_nbo, 0)); } /* * inactivity_shutdown(): shuts down agent if there are no state machines left * to manage * * input: iu_tq_t *: unused * void *: unused * output: void */ /* ARGSUSED */ void inactivity_shutdown(iu_tq_t *tqp, void *arg) { if (smach_count() > 0) /* shouldn't happen, but... */ return; dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out"); iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL); } /* * graceful_shutdown(): shuts down the agent gracefully * * input: int: the signal that caused graceful_shutdown to be called * output: void */ void graceful_shutdown(int sig) { iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE : DHCP_REASON_SIGNAL), drain_script, NULL); } /* * bind_sock(): binds a socket to a given IP address and port number * * input: int: the socket to bind * in_port_t: the port number to bind to, host byte order * in_addr_t: the address to bind to, host byte order * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo) { struct sockaddr_in sin; int on = 1; (void) memset(&sin, 0, sizeof (struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(port_hbo); sin.sin_addr.s_addr = htonl(addr_hbo); (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0); } /* * bind_sock_v6(): binds a socket to a given IP address and port number * * input: int: the socket to bind * in_port_t: the port number to bind to, host byte order * in6_addr_t: the address to bind to, network byte order * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo) { struct sockaddr_in6 sin6; int on = 1; (void) memset(&sin6, 0, sizeof (struct sockaddr_in6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(port_hbo); if (addr_nbo != NULL) { (void) memcpy(&sin6.sin6_addr, addr_nbo, sizeof (sin6.sin6_addr)); } (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0); } /* * valid_hostname(): check whether a string is a valid hostname * * input: const char *: the string to verify as a hostname * output: boolean_t: B_TRUE if the string is a valid hostname * * Note that we accept both host names beginning with a digit and * those containing hyphens. Neither is strictly legal according * to the RFCs, but both are in common practice, so we endeavour * to not break what customers are using. */ static boolean_t valid_hostname(const char *hostname) { unsigned int i; for (i = 0; hostname[i] != '\0'; i++) { if (isalpha(hostname[i]) || isdigit(hostname[i]) || (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0))) continue; return (B_FALSE); } return (i > 0); } /* * iffile_to_hostname(): return the hostname contained on a line of the form * * [ ^I]*inet[ ^I]+hostname[\n]*\0 * * in the file located at the specified path * * input: const char *: the path of the file to look in for the hostname * output: const char *: the hostname at that path, or NULL on failure */ #define IFLINE_MAX 1024 /* maximum length of a hostname.<if> line */ const char * iffile_to_hostname(const char *path) { FILE *fp; static char ifline[IFLINE_MAX]; fp = fopen(path, "r"); if (fp == NULL) return (NULL); /* * /etc/hostname.<if> may contain multiple ifconfig commands, but each * such command is on a separate line (see the "while read ifcmds" code * in /etc/init.d/inetinit). Thus we will read the file a line at a * time, searching for a line of the form * * [ ^I]*inet[ ^I]+hostname[\n]*\0 * * extract the host name from it, and check it for validity. */ while (fgets(ifline, sizeof (ifline), fp) != NULL) { char *p; if ((p = strstr(ifline, "inet")) != NULL) { if ((p != ifline) && !isspace(p[-1])) { (void) fclose(fp); return (NULL); } p += 4; /* skip over "inet" and expect spaces or tabs */ if ((*p == '\n') || (*p == '\0')) { (void) fclose(fp); return (NULL); } if (isspace(*p)) { char *nlptr; /* no need to read more of the file */ (void) fclose(fp); while (isspace(*p)) p++; if ((nlptr = strrchr(p, '\n')) != NULL) *nlptr = '\0'; if (strlen(p) > MAXHOSTNAMELEN) { dhcpmsg(MSG_WARNING, "iffile_to_hostname:" " host name too long"); return (NULL); } if (valid_hostname(p)) { return (p); } else { dhcpmsg(MSG_WARNING, "iffile_to_hostname:" " host name not valid"); return (NULL); } } else { (void) fclose(fp); return (NULL); } } } (void) fclose(fp); return (NULL); } /* * init_timer(): set up a DHCP timer * * input: dhcp_timer_t *: the timer to set up * output: void */ void init_timer(dhcp_timer_t *dt, lease_t startval) { dt->dt_id = -1; dt->dt_start = startval; } /* * cancel_timer(): cancel a DHCP timer * * input: dhcp_timer_t *: the timer to cancel * output: boolean_t: B_TRUE on success, B_FALSE otherwise */ boolean_t cancel_timer(dhcp_timer_t *dt) { if (dt->dt_id == -1) return (B_TRUE); if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) { dt->dt_id = -1; return (B_TRUE); } return (B_FALSE); } /* * schedule_timer(): schedule a DHCP timer. Note that it must not be already * running, and that we can't cancel here. If it were, and * we did, we'd leak a reference to the callback argument. * * input: dhcp_timer_t *: the timer to schedule * output: boolean_t: B_TRUE on success, B_FALSE otherwise */ boolean_t schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg) { if (dt->dt_id != -1) return (B_FALSE); dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg); return (dt->dt_id != -1); } /* * dhcpv6_status_code(): report on a DHCPv6 status code found in an option * buffer. * * input: const dhcpv6_option_t *: pointer to option * uint_t: option length * const char **: error string (nul-terminated) * const char **: message from server (unterminated) * uint_t *: length of server message * output: int: -1 on error, or >= 0 for a DHCPv6 status code */ int dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr, const char **msg, uint_t *msglenp) { uint16_t status; static const char *v6_status[] = { NULL, "Unknown reason", "Server has no addresses available", "Client record unavailable", "Prefix inappropriate for link", "Client must use multicast", "No prefix available" }; static char sbuf[32]; *estr = ""; *msg = ""; *msglenp = 0; if (d6o == NULL) return (0); olen -= sizeof (*d6o); if (olen < 2) { *estr = "garbled status code"; return (-1); } *msg = (const char *)(d6o + 1) + 2; *msglenp = olen - 2; (void) memcpy(&status, d6o + 1, sizeof (status)); status = ntohs(status); if (status > 0) { if (status > DHCPV6_STAT_NOPREFIX) { (void) snprintf(sbuf, sizeof (sbuf), "status %u", status); *estr = sbuf; } else { *estr = v6_status[status]; } } return (status); }