/* * 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 #ifdef __linux__ # include #endif #include #include #include #include #include #include #include #include #include "config.h" #include "common.h" #include "configure.h" #include "dhcp.h" #ifdef ENABLE_INFO # include "info.h" #endif #include "interface.h" #include "dhcpcd.h" #include "logger.h" #include "signal.h" #include "socket.h" #include "logwriter.h" static int file_in_path (const char *file) { char *p = getenv ("PATH"); char *path; char *token; struct stat s; char mypath[PATH_MAX]; int retval = -1; if (! p) { errno = ENOENT; return (-1); } path = strdup (p); p = path; while ((token = strsep (&p, ":"))) { snprintf (mypath, PATH_MAX, "%s/%s", token, file); if (stat (mypath, &s) == 0) { retval = 0; break; } } free (path); return (retval); } /* IMPORTANT: Ensure that the last parameter is NULL when calling */ static int exec_cmd (const char *cmd, const char *args, ...) { va_list va; char **argv; int n = 1; int ret = 0; pid_t pid; sigset_t full; sigset_t old; va_start (va, args); while (va_arg (va, char *) != NULL) n++; va_end (va); argv = xmalloc (sizeof (char *) * (n + 2)); va_start (va, args); n = 2; argv[0] = (char *) cmd; argv[1] = (char *) args; while ((argv[n] = va_arg (va, char *)) != NULL) n++; va_end (va); /* OK, we need to block signals */ sigfillset (&full); sigprocmask (SIG_SETMASK, &full, &old); #ifdef THERE_IS_NO_FORK signal_reset (); pid = vfork (); #else pid = fork(); #endif switch (pid) { case -1: logger (LOG_ERR, "vfork: %s", strerror (errno)); ret = -1; break; case 0: #ifndef THERE_IS_NO_FORK signal_reset (); #endif sigprocmask (SIG_SETMASK, &old, NULL); if (execvp (cmd, argv) && errno != ENOENT) logger (LOG_ERR, "error executing \"%s\": %s", cmd, strerror (errno)); _exit (111); /* NOTREACHED */ } #ifdef THERE_IS_NO_FORK signal_setup (); #endif /* Restore our signals */ sigprocmask (SIG_SETMASK, &old, NULL); free (argv); return (ret); } static void exec_script (const char *script, const char *infofile, const char *arg) { struct stat buf; if (! script || ! infofile || ! arg) return; if (stat (script, &buf) == -1) { if (strcmp (script, DEFAULT_SCRIPT) != 0) logger (LOG_ERR, "`%s': %s", script, strerror (ENOENT)); return; } #ifdef ENABLE_INFO logger (LOG_DEBUG, "exec \"%s\" \"%s\" \"%s\"", script, infofile, arg); exec_cmd (script, infofile, arg, (char *) NULL); #else logger (LOG_DEBUG, "exec \"%s\" \"\" \"%s\"", script, arg); exec_cmd (script, "", arg, (char *) NULL); #endif } static int make_resolv (const char *ifname, const dhcp_t *dhcp) { FILE *f = NULL; address_t *address; #ifdef ENABLE_RESOLVCONF char *resolvconf = NULL; if (file_in_path ("resolvconf") == 0) { size_t len = strlen ("resolvconf -a ") + strlen (ifname) + 1; resolvconf = xmalloc (sizeof (char) * len); snprintf (resolvconf, len, "resolvconf -a %s", ifname); if ((f = popen (resolvconf , "w"))) logger (LOG_DEBUG, "sending DNS information to resolvconf"); else if (errno == EEXIST) logger (LOG_ERR, "popen: %s", strerror (errno)); if (ferror (f)) logger (LOG_ERR, "ferror"); free (resolvconf); } #endif if (! f) { logger (LOG_DEBUG, "writing "RESOLVFILE); if (! (f = fopen(RESOLVFILE, "w"))) logger (LOG_ERR, "fopen `%s': %s", RESOLVFILE, strerror (errno)); } if (! f) return (-1); fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname); if (dhcp->dnssearch) fprintf (f, "search %s\n", dhcp->dnssearch); else if (dhcp->dnsdomain) { fprintf (f, "search %s\n", dhcp->dnsdomain); } STAILQ_FOREACH (address, dhcp->dnsservers, entries) fprintf (f, "nameserver %s\n", inet_ntoa (address->address)); #ifdef ENABLE_RESOLVCONF if (resolvconf) pclose (f); else #endif fclose (f); /* Refresh the local resolver */ res_init (); return (0); } static void restore_resolv (const char *ifname) { #ifdef ENABLE_RESOLVCONF if (file_in_path ("resolvconf") == 0) { logger (LOG_DEBUG, "removing information from resolvconf"); exec_cmd("resolvconf", "-d", ifname, (char *) NULL); } #endif } static bool in_addresses (const struct address_head *addresses, struct in_addr address) { const address_t *addr; STAILQ_FOREACH (addr, addresses, entries) if (addr->address.s_addr == address.s_addr) return (true); return (false); } static bool in_routes (const struct route_head *routes, route_t *route) { const route_t *r; if (! routes) return (false); STAILQ_FOREACH (r, routes, entries) if (r->destination.s_addr == route->destination.s_addr && r->netmask.s_addr == route->netmask.s_addr && r->gateway.s_addr == route->gateway.s_addr) return (true); return (false); } #ifdef ENABLE_NTP static int _make_ntp (const char *file, const char *ifname, const dhcp_t *dhcp) { FILE *f; address_t *address; char *a; char *line; int tomatch = 0; char *token; bool ntp = false; STAILQ_FOREACH (address, dhcp->ntpservers, entries) tomatch++; /* Check that we really need to update the servers. * We do this because ntp has to be restarted to * work with a changed config. */ if (! (f = fopen (file, "r"))) { if (errno != ENOENT) { logger (LOG_ERR, "fopen `%s': %s", file, strerror (errno)); return (-1); } } else { while (tomatch != 0 && (line = get_line (f))) { struct in_addr addr; a = line; token = strsep (&a, " "); if (! token || strcmp (token, "server") != 0) goto next; if ((token = strsep (&a, " \n")) == NULL) goto next; if (inet_aton (token, &addr) == 1 && in_addresses (dhcp->ntpservers, addr)) tomatch--; next: free (line); } fclose (f); /* File has the same name servers that we do, * so no need to restart ntp */ if (tomatch == 0) { logger (LOG_DEBUG, "%s already configured, skipping", file); return (0); } } logger (LOG_DEBUG, "writing %s", file); if (! (f = fopen (file, "w"))) { logger (LOG_ERR, "fopen `%s': %s", file, strerror (errno)); return (-1); } fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname); #ifdef NTPFILE if (strcmp (file, NTPFILE) == 0) { ntp = true; fprintf (f, "restrict default noquery notrust nomodify\n"); fprintf (f, "restrict 127.0.0.1\n"); } #endif STAILQ_FOREACH (address, dhcp->ntpservers, entries) { a = inet_ntoa (address->address); if (ntp) fprintf (f, "restrict %s nomodify notrap noquery\n", a); fprintf (f, "server %s\n", a); } fclose (f); return (1); } static int make_ntp (const char *ifname, const dhcp_t *dhcp) { /* On some systems we have only have one ntp service, but we don't * know which configuration file we're using. So we need to write * to both and restart accordingly. */ bool restart_ntp = false; bool restart_openntp = false; int retval = 0; #ifdef NTPFILE if (_make_ntp (NTPFILE, ifname, dhcp) > 0) restart_ntp = true; #endif #ifdef OPENNTPFILE if (_make_ntp (OPENNTPFILE, ifname, dhcp) > 0) restart_openntp = true; #endif #ifdef NTPSERVICE if (restart_ntp) { #ifdef NTPCHECK if (system (NTPCHECK) == 0) #endif retval += exec_cmd (NTPSERVICE, NTPRESTARTARGS, (char *) NULL); } #endif #if defined (NTPSERVICE) && defined (OPENNTPSERVICE) if (restart_openntp && (strcmp (NTPSERVICE, OPENNTPSERVICE) != 0 || ! restart_ntp)) { #ifdef OPENNTPCHECK if (system (OPENNTPCHECK) == 0) #endif retval += exec_cmd (OPENNTPSERVICE, OPENNTPRESTARTARGS, (char *) NULL); } #elif defined (OPENNTPSERVICE) && ! defined (NTPSERVICE) if (restart_openntp) { #ifdef OPENNTPCHECK if (system (OPENNTPCHECK) == 0) #endif retval += exec_cmd (OPENNTPSERVICE, OPENNTPRESTARTARGS, (char *) NULL); } #endif return (retval); } #endif #ifdef ENABLE_NIS #define PREFIXSIZE 256 static int make_nis (const char *ifname, const dhcp_t *dhcp) { FILE *f; address_t *address; char *prefix; logger (LOG_DEBUG, "writing "NISFILE); if (! (f = fopen(NISFILE, "w"))) { logger (LOG_ERR, "fopen `%s': %s", NISFILE, strerror (errno)); return (-1); } prefix = xmalloc (sizeof (char) * PREFIXSIZE); *prefix = '\0'; fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname); if (dhcp->nisdomain) { setdomainname (dhcp->nisdomain, (int) strlen (dhcp->nisdomain)); if (dhcp->nisservers) snprintf (prefix, PREFIXSIZE, "domain %s server", dhcp->nisdomain); else fprintf (f, "domain %s broadcast\n", dhcp->nisdomain); } else snprintf (prefix, PREFIXSIZE, "%s", "ypserver"); NSTAILQ_FOREACH (address, dhcp->nisservers, entries) fprintf (f, "%s %s\n", prefix, inet_ntoa (address->address)); free (prefix); fclose (f); #ifdef NISCHECK if (system (NISCHECK) == 0) #endif exec_cmd (NISSERVICE, NISRESTARTARGS, (char *) NULL); return (0); } #endif static char *lookuphostname (char *hostname, const dhcp_t *dhcp, const options_t *options) { union { struct sockaddr sa; struct sockaddr_in sin; } su; socklen_t salen; char *addr; struct addrinfo hints; struct addrinfo *res = NULL; int result; char *p; logger (LOG_DEBUG, "Looking up hostname via DNS"); addr = xmalloc (sizeof (char) * NI_MAXHOST); salen = sizeof (su.sa); memset (&su.sa, 0, salen); su.sin.sin_family = AF_INET; memcpy (&su.sin.sin_addr, &dhcp->address, sizeof (su.sin.sin_addr)); if ((result = getnameinfo (&su.sa, salen, addr, NI_MAXHOST, NULL, 0, NI_NAMEREQD)) != 0) { logger (LOG_ERR, "Failed to lookup hostname via DNS: %s", gai_strerror (result)); free (addr); return (NULL); } /* Check for a malicious PTR record */ memset (&hints, 0, sizeof (hints)); hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_NUMERICHOST; result = getaddrinfo (addr, "0", &hints, &res); if (res) freeaddrinfo (res); if (result == 0) logger (LOG_ERR, "malicious PTR record detected"); if (result == 0 || ! *addr) { free (addr); return (NULL); } p = strchr (addr, '.'); if (p) { switch (options->dohostname) { case 1: /* -H */ case 4: /* -HHHH */ break; case 2: /* -HH */ case 5: /* -HHHHH */ /* Strip out the domain if it matches */ p++; if (*p && dhcp->dnssearch) { char *s = xstrdup (dhcp->dnssearch); char *sp = s; char *t; while ((t = strsep (&sp, " "))) if (strcmp (t, p) == 0) { *--p = '\0'; break; } free (s); } else if (dhcp->dnsdomain) { if (strcmp (dhcp->dnsdomain, p) == 0) *--p = '\0'; } break; case 3: /* -HHH */ case 6: /* -HHHHHH */ /* Just strip the domain */ *p = '\0'; break; default: /* Too many H! */ break; } } strlcpy (hostname, addr, MAXHOSTNAMELEN); free (addr); return (hostname); } int configure (const options_t *options, interface_t *iface, const dhcp_t *dhcp, bool up) { route_t *route = NULL; struct route_head *new_routes = NULL; route_t *new_route = NULL; char *newhostname = NULL; char *curhostname = NULL; int remember; #ifdef ENABLE_IPV4LL bool haslinklocal = false; #endif #ifdef THERE_IS_NO_FORK int skip = 0; size_t skiplen; char *skipp; #endif if (! options || ! iface || ! dhcp) return (-1); if (dhcp->address.s_addr == 0) up = 0; logGatewayToFile(iface, dhcp); /* Remove old routes. * Always do this as the interface may have >1 address not added by us * so the routes we added may still exist. */ NSTAILQ_FOREACH (route, iface->previous_routes, entries) if ((route->destination.s_addr || options->dogateway) && (! up || ! in_routes (dhcp->routes, route))) del_route (iface->name, route->destination, route->netmask, route->gateway, options->metric); /* If we aren't up, then reset the interface as much as we can */ if (! up) { if (iface->previous_routes) { free_route (iface->previous_routes); iface->previous_routes = NULL; } /* Restore the original MTU value */ if (iface->mtu && iface->previous_mtu != iface->mtu) { set_mtu (iface->name, iface->mtu); iface->previous_mtu = iface->mtu; } #ifdef ENABLE_INFO /* If we haven't created an info file, do so now */ if (! dhcp->frominfo) write_info (iface, dhcp, options, false); #endif /* Only reset things if we had set them before */ if (iface->previous_address.s_addr != 0) { if (! options->keep_address) { del_address (iface->name, iface->previous_address, iface->previous_netmask); memset (&iface->previous_address, 0, sizeof (iface->previous_address)); memset (&iface->previous_netmask, 0, sizeof (iface->previous_netmask)); } } restore_resolv (iface->name); exec_script (options->script, iface->infofile, "down"); return (0); } /* Set the MTU requested. * If the DHCP server no longer sends one OR it's invalid then * we restore the original MTU */ if (options->domtu) { unsigned short mtu = iface->mtu; if (dhcp->mtu) mtu = dhcp->mtu; if (mtu != iface->previous_mtu) { if (set_mtu (iface->name, mtu) == 0) iface->previous_mtu = mtu; } } /* This also changes netmask */ if (! options->doinform || ! has_address (iface->name, dhcp->address)) if (add_address (iface->name, dhcp->address, dhcp->netmask, dhcp->broadcast) == -1 && errno != EEXIST) return (false); /* Now delete the old address if different */ if (iface->previous_address.s_addr != dhcp->address.s_addr && iface->previous_address.s_addr != 0 && ! options->keep_address) del_address (iface->name, iface->previous_address, iface->previous_netmask); #ifdef __linux__ /* On linux, we need to change the subnet route to have our metric. */ if (iface->previous_address.s_addr != dhcp->address.s_addr && options->metric > 0 && dhcp->netmask.s_addr != INADDR_BROADCAST) { struct in_addr td; struct in_addr tg; memset (&td, 0, sizeof (td)); memset (&tg, 0, sizeof (tg)); td.s_addr = dhcp->address.s_addr & dhcp->netmask.s_addr; add_route (iface->name, td, dhcp->netmask, tg, options->metric); del_route (iface->name, td, dhcp->netmask, tg, 0); } #endif #ifdef THERE_IS_NO_FORK free (dhcpcd_skiproutes); /* We can never have more than 255 routes. So we need space * for 255 3 digit numbers and commas */ skiplen = 255 * 4 + 1; skipp = dhcpcd_skiproutes = xmalloc (sizeof (char) * skiplen); *skipp = '\0'; #endif /* Remember added routes */ NSTAILQ_FOREACH (route, dhcp->routes, entries) { #ifdef ENABLE_IPV4LL /* Check if we have already got a link locale route dished * out by the DHCP server */ if (route->destination.s_addr == htonl (LINKLOCAL_ADDR) && route->netmask.s_addr == htonl (LINKLOCAL_MASK)) haslinklocal = true; #endif /* Don't set default routes if not asked to */ if (route->destination.s_addr == 0 && route->netmask.s_addr == 0 && ! options->dogateway) continue; remember = add_route (iface->name, route->destination, route->netmask, route->gateway, options->metric); /* If we failed to add the route, we may have already added it ourselves. If so, remember it again. */ if (remember < 0 && in_routes (iface->previous_routes, route)) remember = 1; if (remember >= 0) { if (! new_routes) { new_routes = xmalloc (sizeof (*new_routes)); STAILQ_INIT (new_routes); } new_route = xmalloc (sizeof (route_t)); memcpy (new_route, route, sizeof (*new_route)); STAILQ_INSERT_TAIL (new_routes, new_route, entries); } #ifdef THERE_IS_NO_FORK /* If we have daemonised yet we need to record which routes * we failed to add so we can skip them */ else if (! options->daemonised) { /* We can never have more than 255 / 4 routes, * so 3 chars is plently */ if (*skipp) *skipp++ = ','; skipp += snprintf (skipp, dhcpcd_skiproutes + skiplen - skipp, "%d", skip); } skip++; #endif } #ifdef THERE_IS_NO_FORK if (*dhcpcd_skiproutes) *skipp = '\0'; else { free (dhcpcd_skiproutes); dhcpcd_skiproutes = NULL; } #endif #ifdef ENABLE_IPV4LL /* Ensure we always add the link local route if we got a private * address and isn't link local itself */ if (options->doipv4ll && ! haslinklocal && IN_PRIVATE (ntohl (dhcp->address.s_addr))) { struct in_addr dest; struct in_addr mask; struct in_addr gate; dest.s_addr = htonl (LINKLOCAL_ADDR); mask.s_addr = htonl (LINKLOCAL_MASK); gate.s_addr = 0; remember = add_route (iface->name, dest, mask, gate, options->metric); if (remember >= 0) { if (! new_routes) { new_routes = xmalloc (sizeof (*new_routes)); STAILQ_INIT (new_routes); } new_route = xmalloc (sizeof (*new_route)); new_route->destination.s_addr = dest.s_addr; new_route->netmask.s_addr = mask.s_addr; new_route->gateway.s_addr = gate.s_addr; STAILQ_INSERT_TAIL (new_routes, new_route, entries); } } #endif if (iface->previous_routes) free_route (iface->previous_routes); iface->previous_routes = new_routes; logToQt(LOG_INFO, DHCPCD_WRITE, ""); if (options->dodns && dhcp->dnsservers) make_resolv(iface->name, dhcp); else logger (LOG_DEBUG, "no dns information to write"); #ifdef ENABLE_NTP if (options->dontp && dhcp->ntpservers) make_ntp(iface->name, dhcp); #endif #ifdef ENABLE_NIS if (options->donis && (dhcp->nisservers || dhcp->nisdomain)) make_nis(iface->name, dhcp); #endif curhostname = xmalloc (sizeof (char) * MAXHOSTNAMELEN); *curhostname = '\0'; gethostname (curhostname, MAXHOSTNAMELEN); if (options->dohostname || strlen (curhostname) == 0 || strcmp (curhostname, "(none)") == 0 || strcmp (curhostname, "localhost") == 0) { newhostname = xmalloc (sizeof (char) * MAXHOSTNAMELEN); if (dhcp->hostname) strlcpy (newhostname, dhcp->hostname, MAXHOSTNAMELEN); else *newhostname = '\0'; /* Now we have made a resolv.conf we can obtain a hostname * if we need it */ if (! *newhostname || options->dohostname > 3) lookuphostname (newhostname, dhcp, options); if (*newhostname) { logger (LOG_INFO, "setting hostname to `%s'", newhostname); sethostname (newhostname, (int) strlen (newhostname)); } free (newhostname); } free (curhostname); #ifdef ENABLE_INFO if (! dhcp->frominfo) write_info (iface, dhcp, options, true); #endif if (iface->previous_address.s_addr != dhcp->address.s_addr || iface->previous_netmask.s_addr != dhcp->netmask.s_addr) { memcpy (&iface->previous_address, &dhcp->address, sizeof (iface->previous_address)); memcpy (&iface->previous_netmask, &dhcp->netmask, sizeof (iface->previous_netmask)); exec_script (options->script, iface->infofile, "new"); } else exec_script (options->script, iface->infofile, "up"); return (0); }