/* * dhcpcd - DHCP client daemon * Copyright 2006-2008 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "config.h" #include "common.h" #include "configure.h" #include "dhcp.h" #include "dhcpcd.h" #include "logger.h" #include "net.h" #include "signals.h" #define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin" static int exec_script(char *const *argv, char *const *env) { pid_t pid; sigset_t full; sigset_t old; /* OK, we need to block signals */ sigfillset(&full); sigprocmask(SIG_SETMASK, &full, &old); signal_reset(); switch (pid = vfork()) { case -1: logger(LOG_ERR, "vfork: %s", strerror(errno)); break; case 0: sigprocmask(SIG_SETMASK, &old, NULL); execve(argv[0], argv, env); logger(LOG_ERR, "%s: %s", argv[0], strerror(errno)); _exit(127); /* NOTREACHED */ } /* Restore our signals */ signal_setup(); sigprocmask(SIG_SETMASK, &old, NULL); return pid; } int run_script(const struct options *options, const char *iface, const char *reason, const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo) { char *const argv[2] = { UNCONST(options->script), NULL }; char **env = NULL, **ep; char *path; ssize_t e, elen; pid_t pid; int status = 0; logger(LOG_DEBUG, "executing `%s', reason %s", options->script, reason); /* Make our env */ elen = 5; env = xmalloc(sizeof(char *) * (elen + 1)); path = getenv("PATH"); if (path) { e = strlen("PATH") + strlen(path) + 2; env[0] = xmalloc(e); snprintf(env[0], e, "PATH=%s", path); } else env[0] = xstrdup(DEFAULT_PATH); e = strlen("interface") + strlen(iface) + 2; env[1] = xmalloc(e); snprintf(env[1], e, "interface=%s", iface); e = strlen("reason") + strlen(reason) + 2; env[2] = xmalloc(e); snprintf(env[2], e, "reason=%s", reason); e = 20; env[3] = xmalloc(e); snprintf(env[3], e, "pid=%d", getpid()); env[4] = xmalloc(e); snprintf(env[4], e, "metric=%d", options->metric); if (dhcpo) { e = configure_env(NULL, NULL, dhcpo, options); if (e > 0) { env = xrealloc(env, sizeof(char *) * (elen + e + 1)); elen += configure_env(env + elen, "old", dhcpo, options); } } if (dhcpn) { e = configure_env(NULL, NULL, dhcpn, options); if (e > 0) { env = xrealloc(env, sizeof(char *) * (elen + e + 1)); elen += configure_env(env + elen, "new", dhcpn, options); } } /* Add our base environment */ if (options->environ) { e = 0; while (options->environ[e++]) ; env = xrealloc(env, sizeof(char *) * (elen + e + 1)); e = 0; while (options->environ[e]) { env[elen + e] = xstrdup(options->environ[e]); e++; } elen += e; } env[elen] = '\0'; pid = exec_script(argv, env); if (pid == -1) status = -1; else if (pid != 0) { /* Wait for the script to finish */ while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { logger(LOG_ERR, "waitpid: %s", strerror(errno)); status = -1; break; } } } /* Cleanup */ ep = env; while (*ep) free(*ep++); free(env); return status; } static struct rt * reverse_routes(struct rt *routes) { struct rt *rt; struct rt *rtn = NULL; while (routes) { rt = routes->next; routes->next = rtn; rtn = routes; routes = rt; } return rtn; } static int delete_route(const char *iface, struct rt *rt, int metric) { char *addr; int retval; addr = xstrdup(inet_ntoa(rt->dest)); logger(LOG_DEBUG, "deleting route %s/%d via %s", addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); free(addr); retval = del_route(iface, &rt->dest, &rt->net, &rt->gate, metric); if (retval != 0 && errno != ENOENT && errno != ESRCH) logger(LOG_ERR," del_route: %s", strerror(errno)); return retval; } static int delete_routes(struct interface *iface, int metric) { struct rt *rt; struct rt *rtn; int retval = 0; rt = reverse_routes(iface->routes); while (rt) { rtn = rt->next; retval += delete_route(iface->name, rt, metric); free(rt); rt = rtn; } iface->routes = NULL; return retval; } static int in_routes(const struct rt *routes, const struct rt *rt) { while (routes) { if (routes->dest.s_addr == rt->dest.s_addr && routes->net.s_addr == rt->net.s_addr && routes->gate.s_addr == rt->gate.s_addr) return 0; routes = routes->next; } return -1; } static int configure_routes(struct interface *iface, const struct dhcp_message *dhcp, const struct options *options) { struct rt *rt, *ort; struct rt *rtn = NULL, *nr = NULL; int remember; int retval = 0; char *addr; ort = get_option_routes(dhcp); #ifdef IPV4LL_ALWAYSROUTE if (options->options & DHCPCD_IPV4LL && IN_PRIVATE(ntohl(dhcp->yiaddr))) { for (rt = ort; rt; rt = rt->next) { /* Check if we have already got a link locale route * dished out by the DHCP server */ if (rt->dest.s_addr == htonl(LINKLOCAL_ADDR) && rt->net.s_addr == htonl(LINKLOCAL_MASK)) break; rtn = rt; } if (!rt) { rt = xmalloc(sizeof(*rt)); rt->dest.s_addr = htonl(LINKLOCAL_ADDR); rt->net.s_addr = htonl(LINKLOCAL_MASK); rt->gate.s_addr = 0; rt->next = NULL; if (rtn) rtn->next = rt; else ort = rt; } } #endif /* Now remove old routes we no longer use. * We should do this in reverse order. */ iface->routes = reverse_routes(iface->routes); for (rt = iface->routes; rt; rt = rt->next) if (in_routes(ort, rt) != 0) delete_route(iface->name, rt, options->metric); for (rt = ort; rt; rt = rt->next) { /* Don't set default routes if not asked to */ if (rt->dest.s_addr == 0 && rt->net.s_addr == 0 && !(options->options & DHCPCD_GATEWAY)) continue; addr = xstrdup(inet_ntoa(rt->dest)); logger(LOG_DEBUG, "adding route to %s/%d via %s", addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); free(addr); remember = add_route(iface->name, &rt->dest, &rt->net, &rt->gate, options->metric); retval += remember; /* If we failed to add the route, we may have already added it ourselves. If so, remember it again. */ if (remember < 0) { if (errno != EEXIST) logger(LOG_ERR, "add_route: %s", strerror(errno)); if (in_routes(iface->routes, rt) == 0) remember = 1; } /* This login is split from above due to the #ifdef below */ if (remember >= 0) { if (nr) { rtn->next = xmalloc(sizeof(*rtn)); rtn = rtn->next; } else { nr = rtn = xmalloc(sizeof(*rtn)); } rtn->dest.s_addr = rt->dest.s_addr; rtn->net.s_addr = rt->net.s_addr; rtn->gate.s_addr = rt->gate.s_addr; rtn->next = NULL; } } free_routes(ort); free_routes(iface->routes); iface->routes = nr; return retval; } static int delete_address(struct interface *iface) { int retval; logger(LOG_DEBUG, "deleting IP address %s/%d", inet_ntoa(iface->addr), inet_ntocidr(iface->net)); retval = del_address(iface->name, &iface->addr, &iface->net); if (retval == -1 && errno != EADDRNOTAVAIL) logger(LOG_ERR, "del_address: %s", strerror(errno)); iface->addr.s_addr = 0; iface->net.s_addr = 0; return retval; } int configure(struct interface *iface, const char *reason, const struct dhcp_message *dhcp, const struct dhcp_message *old, const struct dhcp_lease *lease, const struct options *options, int up) { struct in_addr addr; struct in_addr net; struct in_addr brd; #ifdef __linux__ struct in_addr dest; struct in_addr gate; #endif /* Grab our IP config */ if (dhcp == NULL) up = 0; else { addr.s_addr = dhcp->yiaddr; if (addr.s_addr == 0) addr.s_addr = lease->addr.s_addr; /* Ensure we have all the needed values */ if (get_option_addr(&net.s_addr, dhcp, DHO_SUBNETMASK) == -1) net.s_addr = get_netmask(addr.s_addr); if (get_option_addr(&brd.s_addr, dhcp, DHO_BROADCAST) == -1) brd.s_addr = addr.s_addr | ~net.s_addr; } /* If we aren't up, then reset the interface as much as we can */ if (!up) { /* Only reset things if we had set them before */ if (iface->addr.s_addr != 0) { delete_routes(iface, options->metric); delete_address(iface); } run_script(options, iface->name, reason, NULL, old); return 0; } /* This also changes netmask */ if (!(options->options & DHCPCD_INFORM) || !has_address(iface->name, &addr, &net)) { logger(LOG_DEBUG, "adding IP address %s/%d", inet_ntoa(addr), inet_ntocidr(net)); if (add_address(iface->name, &addr, &net, &brd) == -1 && errno != EEXIST) { logger(LOG_ERR, "add_address: %s", strerror(errno)); return -1; } } /* Now delete the old address if different */ if (iface->addr.s_addr != addr.s_addr && iface->addr.s_addr != 0) delete_address(iface); #ifdef __linux__ /* On linux, we need to change the subnet route to have our metric. */ if (iface->addr.s_addr != lease->addr.s_addr && options->metric > 0 && net.s_addr != INADDR_BROADCAST) { dest.s_addr = addr.s_addr & net.s_addr; gate.s_addr = 0; add_route(iface->name, &dest, &net, &gate, options->metric); del_route(iface->name, &dest, &net, &gate, 0); } #endif configure_routes(iface, dhcp, options); up = (iface->addr.s_addr != addr.s_addr || iface->net.s_addr != net.s_addr); iface->addr.s_addr = addr.s_addr; iface->net.s_addr = net.s_addr; if (!lease->frominfo) if (write_lease(iface, dhcp) == -1) logger(LOG_ERR, "write_lease: %s", strerror(errno)); run_script(options, iface->name, reason, dhcp, old); return 0; }