/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2021 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. */ static const char dhcpcd_copyright[] = "Copyright (c) 2006-2021 Roy Marples"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "arp.h" #include "common.h" #include "control.h" #include "dev.h" #include "dhcp-common.h" #include "dhcpcd.h" #include "dhcp.h" #include "dhcp6.h" #include "duid.h" #include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "ipv4ll.h" #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" #include "privsep.h" #include "script.h" #ifdef HAVE_CAPSICUM #include #endif #ifdef HAVE_UTIL_H #include #endif #ifdef USE_SIGNALS const int dhcpcd_signals[] = { SIGTERM, SIGINT, SIGALRM, SIGHUP, SIGUSR1, SIGUSR2, SIGCHLD, }; const size_t dhcpcd_signals_len = __arraycount(dhcpcd_signals); const int dhcpcd_signals_ignore[] = { SIGPIPE, }; const size_t dhcpcd_signals_ignore_len = __arraycount(dhcpcd_signals_ignore); #endif const char *dhcpcd_default_script = SCRIPT; static void usage(void) { printf("usage: "PACKAGE"\t[-146ABbDdEGgHJKLMNPpqTV]\n" "\t\t[-C, --nohook hook] [-c, --script script]\n" "\t\t[-e, --env value] [-F, --fqdn FQDN] [-f, --config file]\n" "\t\t[-h, --hostname hostname] [-I, --clientid clientid]\n" "\t\t[-i, --vendorclassid vendorclassid] [-j, --logfile logfile]\n" "\t\t[-l, --leasetime seconds] [-m, --metric metric]\n" "\t\t[-O, --nooption option] [-o, --option option]\n" "\t\t[-Q, --require option] [-r, --request address]\n" "\t\t[-S, --static value]\n" "\t\t[-s, --inform address[/cidr[/broadcast_address]]]\n [--inform6]" "\t\t[-t, --timeout seconds] [-u, --userclass class]\n" "\t\t[-v, --vendor code, value] [-W, --whitelist address[/cidr]] [-w]\n" "\t\t[--waitip [4 | 6]] [-y, --reboot seconds]\n" "\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n" "\t\t[-z, --allowinterfaces pattern] [--inactive] [interface] [...]\n" " "PACKAGE"\t-n, --rebind [interface]\n" " "PACKAGE"\t-k, --release [interface]\n" " "PACKAGE"\t-U, --dumplease interface\n" " "PACKAGE"\t--version\n" " "PACKAGE"\t-x, --exit [interface]\n"); } static void free_globals(struct dhcpcd_ctx *ctx) { struct dhcp_opt *opt; if (ctx->ifac) { for (; ctx->ifac > 0; ctx->ifac--) free(ctx->ifav[ctx->ifac - 1]); free(ctx->ifav); ctx->ifav = NULL; } if (ctx->ifdc) { for (; ctx->ifdc > 0; ctx->ifdc--) free(ctx->ifdv[ctx->ifdc - 1]); free(ctx->ifdv); ctx->ifdv = NULL; } if (ctx->ifcc) { for (; ctx->ifcc > 0; ctx->ifcc--) free(ctx->ifcv[ctx->ifcc - 1]); free(ctx->ifcv); ctx->ifcv = NULL; } #ifdef INET if (ctx->dhcp_opts) { for (opt = ctx->dhcp_opts; ctx->dhcp_opts_len > 0; opt++, ctx->dhcp_opts_len--) free_dhcp_opt_embenc(opt); free(ctx->dhcp_opts); ctx->dhcp_opts = NULL; } #endif #ifdef INET6 if (ctx->nd_opts) { for (opt = ctx->nd_opts; ctx->nd_opts_len > 0; opt++, ctx->nd_opts_len--) free_dhcp_opt_embenc(opt); free(ctx->nd_opts); ctx->nd_opts = NULL; } #ifdef DHCP6 if (ctx->dhcp6_opts) { for (opt = ctx->dhcp6_opts; ctx->dhcp6_opts_len > 0; opt++, ctx->dhcp6_opts_len--) free_dhcp_opt_embenc(opt); free(ctx->dhcp6_opts); ctx->dhcp6_opts = NULL; } #endif #endif if (ctx->vivso) { for (opt = ctx->vivso; ctx->vivso_len > 0; opt++, ctx->vivso_len--) free_dhcp_opt_embenc(opt); free(ctx->vivso); ctx->vivso = NULL; } } static void handle_exit_timeout(void *arg) { struct dhcpcd_ctx *ctx; ctx = arg; logerrx("timed out"); if (!(ctx->options & DHCPCD_MANAGER)) { struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (ifp->active == IF_ACTIVE_USER) script_runreason(ifp, "STOPPED"); } eloop_exit(ctx->eloop, EXIT_FAILURE); return; } ctx->options |= DHCPCD_NOWAITIP; dhcpcd_daemonise(ctx); } static const char * dhcpcd_af(int af) { switch (af) { case AF_UNSPEC: return "IP"; case AF_INET: return "IPv4"; case AF_INET6: return "IPv6"; default: return NULL; } } int dhcpcd_ifafwaiting(const struct interface *ifp) { unsigned long long opts; bool foundany = false; if (ifp->active != IF_ACTIVE_USER) return AF_MAX; #define DHCPCD_WAITALL (DHCPCD_WAITIP4 | DHCPCD_WAITIP6) opts = ifp->options->options; #ifdef INET if (opts & DHCPCD_WAITIP4 || (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL))) { bool foundaddr = ipv4_hasaddr(ifp); if (opts & DHCPCD_WAITIP4 && !foundaddr) return AF_INET; if (foundaddr) foundany = true; } #endif #ifdef INET6 if (opts & DHCPCD_WAITIP6 || (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL))) { bool foundaddr = ipv6_hasaddr(ifp); if (opts & DHCPCD_WAITIP6 && !foundaddr) return AF_INET; if (foundaddr) foundany = true; } #endif if (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL) && !foundany) return AF_UNSPEC; return AF_MAX; } int dhcpcd_afwaiting(const struct dhcpcd_ctx *ctx) { unsigned long long opts; const struct interface *ifp; int af; if (!(ctx->options & DHCPCD_WAITOPTS)) return AF_MAX; opts = ctx->options; TAILQ_FOREACH(ifp, ctx->ifaces, next) { #ifdef INET if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP4) && ipv4_hasaddr(ifp)) opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP4); #endif #ifdef INET6 if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP6) && ipv6_hasaddr(ifp)) opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP6); #endif if (!(opts & DHCPCD_WAITOPTS)) break; } if (opts & DHCPCD_WAITIP) af = AF_UNSPEC; else if (opts & DHCPCD_WAITIP4) af = AF_INET; else if (opts & DHCPCD_WAITIP6) af = AF_INET6; else return AF_MAX; return af; } static int dhcpcd_ipwaited(struct dhcpcd_ctx *ctx) { struct interface *ifp; int af; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) { logdebugx("%s: waiting for an %s address", ifp->name, dhcpcd_af(af)); return 0; } } if ((af = dhcpcd_afwaiting(ctx)) != AF_MAX) { logdebugx("waiting for an %s address", dhcpcd_af(af)); return 0; } return 1; } /* Returns the pid of the child, otherwise 0. */ void dhcpcd_daemonise(struct dhcpcd_ctx *ctx) { #ifdef THERE_IS_NO_FORK eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); errno = ENOSYS; return; #else int i; unsigned int logopts = loggetopts(); if (ctx->options & DHCPCD_DAEMONISE && !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) { if (!dhcpcd_ipwaited(ctx)) return; } if (ctx->options & DHCPCD_ONESHOT) { loginfox("exiting due to oneshot"); eloop_exit(ctx->eloop, EXIT_SUCCESS); return; } eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); if (ctx->options & DHCPCD_DAEMONISED || !(ctx->options & DHCPCD_DAEMONISE)) return; /* Don't use loginfo because this makes no sense in a log. */ if (!(logopts & LOGERR_QUIET) && ctx->stderr_valid) (void)fprintf(stderr, "forked to background, child pid %d\n", getpid()); i = EXIT_SUCCESS; if (write(ctx->fork_fd, &i, sizeof(i)) == -1) logerr("write"); ctx->options |= DHCPCD_DAEMONISED; eloop_event_delete(ctx->eloop, ctx->fork_fd); close(ctx->fork_fd); ctx->fork_fd = -1; /* * Stop writing to stderr. * On the happy path, only the manager process writes to stderr, * so this just stops wasting fprintf calls to nowhere. * All other calls - ie errors in privsep processes or script output, * will error when printing. * If we *really* want to fix that, then we need to suck * stderr/stdout in the manager process and either disacrd it or pass * it to the launcher process and then to stderr. */ logopts &= ~LOGERR_ERR; logsetopts(logopts); #endif } static void dhcpcd_drop(struct interface *ifp, int stop) { #ifdef DHCP6 dhcp6_drop(ifp, stop ? NULL : "EXPIRE6"); #endif #ifdef INET6 ipv6nd_drop(ifp); ipv6_drop(ifp); #endif #ifdef IPV4LL ipv4ll_drop(ifp); #endif #ifdef INET dhcp_drop(ifp, stop ? "STOP" : "EXPIRE"); #endif #ifdef ARP arp_drop(ifp); #endif #if !defined(DHCP6) && !defined(DHCP) UNUSED(stop); #endif } static void stop_interface(struct interface *ifp, const char *reason) { struct dhcpcd_ctx *ctx; ctx = ifp->ctx; loginfox("%s: removing interface", ifp->name); ifp->options->options |= DHCPCD_STOPPING; dhcpcd_drop(ifp, 1); script_runreason(ifp, reason == NULL ? "STOPPED" : reason); /* Delete all timeouts for the interfaces */ eloop_q_timeout_delete(ctx->eloop, ELOOP_QUEUE_ALL, NULL, ifp); /* De-activate the interface */ ifp->active = IF_INACTIVE; ifp->options->options &= ~DHCPCD_STOPPING; if (!(ctx->options & (DHCPCD_MANAGER | DHCPCD_TEST))) eloop_exit(ctx->eloop, EXIT_FAILURE); } static void configure_interface1(struct interface *ifp) { struct if_options *ifo = ifp->options; /* Do any platform specific configuration */ if_conf(ifp); /* If we want to release a lease, we can't really persist the * address either. */ if (ifo->options & DHCPCD_RELEASE) ifo->options &= ~DHCPCD_PERSISTENT; if (ifp->flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) { ifo->options &= ~DHCPCD_ARP; if (!(ifp->flags & IFF_MULTICAST)) ifo->options &= ~DHCPCD_IPV6RS; if (!(ifo->options & (DHCPCD_INFORM | DHCPCD_WANTDHCP))) ifo->options |= DHCPCD_STATIC; } if (ifo->metric != -1) ifp->metric = (unsigned int)ifo->metric; #ifdef INET6 /* We want to setup INET6 on the interface as soon as possible. */ if (ifp->active == IF_ACTIVE_USER && ifo->options & DHCPCD_IPV6 && !(ifp->ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) { /* If not doing any DHCP, disable the RDNSS requirement. */ if (!(ifo->options & (DHCPCD_DHCP | DHCPCD_DHCP6))) ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; if_setup_inet6(ifp); } #endif if (!(ifo->options & DHCPCD_IAID)) { /* * An IAID is for identifying a unqiue interface within * the client. It is 4 bytes long. Working out a default * value is problematic. * * Interface name and number are not stable * between different OS's. Some OS's also cannot make * up their mind what the interface should be called * (yes, udev, I'm looking at you). * Also, the name could be longer than 4 bytes. * Also, with pluggable interfaces the name and index * could easily get swapped per actual interface. * * The MAC address is 6 bytes long, the final 3 * being unique to the manufacturer and the initial 3 * being unique to the organisation which makes it. * We could use the last 4 bytes of the MAC address * as the IAID as it's the most stable part given the * above, but equally it's not guaranteed to be * unique. * * Given the above, and our need to reliably work * between reboots without persitent storage, * generating the IAID from the MAC address is the only * logical default. * Saying that, if a VLANID has been specified then we * can use that. It's possible that different interfaces * can have the same VLANID, but this is no worse than * generating the IAID from the duplicate MAC address. * * dhclient uses the last 4 bytes of the MAC address. * dibbler uses an increamenting counter. * wide-dhcpv6 uses 0 or a configured value. * odhcp6c uses 1. * Windows 7 uses the first 3 bytes of the MAC address * and an unknown byte. * dhcpcd-6.1.0 and earlier used the interface name, * falling back to interface index if name > 4. */ if (ifp->vlanid != 0) { uint32_t vlanid; /* Maximal VLANID is 4095, so prefix with 0xff * so we don't conflict with an interface index. */ vlanid = htonl(ifp->vlanid | 0xff000000); memcpy(ifo->iaid, &vlanid, sizeof(vlanid)); } else if (ifo->options & DHCPCD_ANONYMOUS) memset(ifo->iaid, 0, sizeof(ifo->iaid)); else if (ifp->hwlen >= sizeof(ifo->iaid)) { memcpy(ifo->iaid, ifp->hwaddr + ifp->hwlen - sizeof(ifo->iaid), sizeof(ifo->iaid)); } else { uint32_t len; len = (uint32_t)strlen(ifp->name); if (len <= sizeof(ifo->iaid)) { memcpy(ifo->iaid, ifp->name, len); if (len < sizeof(ifo->iaid)) memset(ifo->iaid + len, 0, sizeof(ifo->iaid) - len); } else { /* IAID is the same size as a uint32_t */ len = htonl(ifp->index); memcpy(ifo->iaid, &len, sizeof(ifo->iaid)); } } ifo->options |= DHCPCD_IAID; } #ifdef DHCP6 if (ifo->ia_len == 0 && ifo->options & DHCPCD_IPV6 && ifp->name[0] != '\0') { ifo->ia = malloc(sizeof(*ifo->ia)); if (ifo->ia == NULL) logerr(__func__); else { ifo->ia_len = 1; ifo->ia->ia_type = D6_OPTION_IA_NA; memcpy(ifo->ia->iaid, ifo->iaid, sizeof(ifo->iaid)); memset(&ifo->ia->addr, 0, sizeof(ifo->ia->addr)); #ifndef SMALL ifo->ia->sla = NULL; ifo->ia->sla_len = 0; #endif } } else { size_t i; for (i = 0; i < ifo->ia_len; i++) { if (!ifo->ia[i].iaid_set) { memcpy(&ifo->ia[i].iaid, ifo->iaid, sizeof(ifo->ia[i].iaid)); ifo->ia[i].iaid_set = 1; } } } #endif /* If root is network mounted, we don't want to kill the connection * if the DHCP server goes the way of the dodo OR dhcpcd is rebooting * and the lease file has expired. */ if (is_root_local() == 0) ifo->options |= DHCPCD_LASTLEASE_EXTEND; } int dhcpcd_selectprofile(struct interface *ifp, const char *profile) { struct if_options *ifo; char pssid[PROFILE_LEN]; if (ifp->ssid_len) { ssize_t r; r = print_string(pssid, sizeof(pssid), OT_ESCSTRING, ifp->ssid, ifp->ssid_len); if (r == -1) { logerr(__func__); pssid[0] = '\0'; } } else pssid[0] = '\0'; ifo = read_config(ifp->ctx, ifp->name, pssid, profile); if (ifo == NULL) { logdebugx("%s: no profile %s", ifp->name, profile); return -1; } if (profile != NULL) { strlcpy(ifp->profile, profile, sizeof(ifp->profile)); loginfox("%s: selected profile %s", ifp->name, profile); } else *ifp->profile = '\0'; free_options(ifp->ctx, ifp->options); ifp->options = ifo; if (profile) { add_options(ifp->ctx, ifp->name, ifp->options, ifp->ctx->argc, ifp->ctx->argv); configure_interface1(ifp); } return 1; } static void configure_interface(struct interface *ifp, int argc, char **argv, unsigned long long options) { time_t old; old = ifp->options ? ifp->options->mtime : 0; dhcpcd_selectprofile(ifp, NULL); if (ifp->options == NULL) { /* dhcpcd cannot continue with this interface. */ ifp->active = IF_INACTIVE; return; } add_options(ifp->ctx, ifp->name, ifp->options, argc, argv); ifp->options->options |= options; configure_interface1(ifp); /* If the mtime has changed drop any old lease */ if (old != 0 && ifp->options->mtime != old) { logwarnx("%s: config file changed, expiring leases", ifp->name); dhcpcd_drop(ifp, 0); } } static void dhcpcd_initstate2(struct interface *ifp, unsigned long long options) { struct if_options *ifo; if (options) { if ((ifo = default_config(ifp->ctx)) == NULL) { logerr(__func__); return; } ifo->options |= options; free(ifp->options); ifp->options = ifo; } else ifo = ifp->options; #ifdef INET6 if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == -1) { logerr(__func__); ifo->options &= ~DHCPCD_IPV6; } #endif } static void dhcpcd_initstate1(struct interface *ifp, int argc, char **argv, unsigned long long options) { configure_interface(ifp, argc, argv, options); if (ifp->active) dhcpcd_initstate2(ifp, 0); } static void dhcpcd_initstate(struct interface *ifp, unsigned long long options) { dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options); } static void dhcpcd_reportssid(struct interface *ifp) { char pssid[IF_SSIDLEN * 4]; if (print_string(pssid, sizeof(pssid), OT_ESCSTRING, ifp->ssid, ifp->ssid_len) == -1) { logerr(__func__); return; } loginfox("%s: connected to Access Point: %s", ifp->name, pssid); } static void dhcpcd_nocarrier_roaming(struct interface *ifp) { loginfox("%s: carrier lost - roaming", ifp->name); #ifdef ARP arp_drop(ifp); #endif #ifdef INET dhcp_abort(ifp); #endif #ifdef DHCP6 dhcp6_abort(ifp); #endif rt_build(ifp->ctx, AF_UNSPEC); script_runreason(ifp, "NOCARRIER_ROAMING"); } void dhcpcd_handlecarrier(struct interface *ifp, int carrier, unsigned int flags) { bool was_link_up = if_is_link_up(ifp); bool was_roaming = if_roaming(ifp); ifp->carrier = carrier; ifp->flags = flags; if (!if_is_link_up(ifp)) { if (!ifp->active || (!was_link_up && !was_roaming)) return; /* * If the interface is roaming (generally on wireless) * then while we are not up, we are not down either. * Preserve the network state until we either disconnect * or re-connect. */ if (!ifp->options->randomise_hwaddr && if_roaming(ifp)) { dhcpcd_nocarrier_roaming(ifp); return; } loginfox("%s: carrier lost", ifp->name); script_runreason(ifp, "NOCARRIER"); dhcpcd_drop(ifp, 0); if (ifp->options->randomise_hwaddr) { bool is_up = ifp->flags & IFF_UP; if (is_up) if_down(ifp); if (if_randomisemac(ifp) == -1 && errno != ENXIO) logerr(__func__); if (is_up) if_up(ifp); } return; } /* * At this point carrier is NOT DOWN and we have IFF_UP. * We should treat LINK_UNKNOWN as up as the driver may not support * link state changes. * The consideration of any other information about carrier should * be handled in the OS specific if_carrier() function. */ if (was_link_up) return; if (ifp->active) { if (carrier == LINK_UNKNOWN) loginfox("%s: carrier unknown, assuming up", ifp->name); else loginfox("%s: carrier acquired", ifp->name); } #if !defined(__linux__) && !defined(__NetBSD__) /* BSD does not emit RTM_NEWADDR or RTM_CHGADDR when the * hardware address changes so we have to go * through the disovery process to work it out. */ dhcpcd_handleinterface(ifp->ctx, 0, ifp->name); #endif if (ifp->wireless) { uint8_t ossid[IF_SSIDLEN]; size_t olen; olen = ifp->ssid_len; memcpy(ossid, ifp->ssid, ifp->ssid_len); if_getssid(ifp); /* If we changed SSID network, drop leases */ if ((ifp->ssid_len != olen || memcmp(ifp->ssid, ossid, ifp->ssid_len)) && ifp->active) { dhcpcd_reportssid(ifp); dhcpcd_drop(ifp, 0); #ifdef IPV4LL ipv4ll_reset(ifp); #endif } } if (!ifp->active) return; dhcpcd_initstate(ifp, 0); script_runreason(ifp, "CARRIER"); #ifdef INET6 /* Set any IPv6 Routers we remembered to expire faster than they * would normally as we maybe on a new network. */ ipv6nd_startexpire(ifp); #ifdef IPV6_MANAGETEMPADDR /* RFC4941 Section 3.5 */ ipv6_regentempaddrs(ifp); #endif #endif dhcpcd_startinterface(ifp); } static void warn_iaid_conflict(struct interface *ifp, uint16_t ia_type, uint8_t *iaid) { struct interface *ifn; #ifdef INET6 size_t i; struct if_ia *ia; #endif TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { if (ifn == ifp || !ifn->active) continue; if (ifn->options->options & DHCPCD_ANONYMOUS) continue; if (ia_type == 0 && memcmp(ifn->options->iaid, iaid, sizeof(ifn->options->iaid)) == 0) break; #ifdef INET6 for (i = 0; i < ifn->options->ia_len; i++) { ia = &ifn->options->ia[i]; if (ia->ia_type == ia_type && memcmp(ia->iaid, iaid, sizeof(ia->iaid)) == 0) break; } #endif } /* This is only a problem if the interfaces are on the same network. */ if (ifn) logerrx("%s: IAID conflicts with one assigned to %s", ifp->name, ifn->name); } static void dhcpcd_initduid(struct dhcpcd_ctx *ctx, struct interface *ifp) { char buf[DUID_LEN * 3]; if (ctx->duid != NULL) { if (ifp == NULL) goto log; return; } duid_init(ctx, ifp); if (ctx->duid == NULL) return; log: loginfox("DUID %s", hwaddr_ntoa(ctx->duid, ctx->duid_len, buf, sizeof(buf))); } void dhcpcd_startinterface(void *arg) { struct interface *ifp = arg; struct if_options *ifo = ifp->options; if (ifo->options & DHCPCD_LINK && !if_is_link_up(ifp)) { loginfox("%s: waiting for carrier", ifp->name); return; } if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6) && !(ifo->options & DHCPCD_ANONYMOUS)) { char buf[sizeof(ifo->iaid) * 3]; #ifdef INET6 size_t i; struct if_ia *ia; #endif /* Try and init DUID from the interface hardware address */ dhcpcd_initduid(ifp->ctx, ifp); /* Report IAIDs */ loginfox("%s: IAID %s", ifp->name, hwaddr_ntoa(ifo->iaid, sizeof(ifo->iaid), buf, sizeof(buf))); warn_iaid_conflict(ifp, 0, ifo->iaid); #ifdef INET6 for (i = 0; i < ifo->ia_len; i++) { ia = &ifo->ia[i]; if (memcmp(ifo->iaid, ia->iaid, sizeof(ifo->iaid))) { loginfox("%s: IA type %u IAID %s", ifp->name, ia->ia_type, hwaddr_ntoa(ia->iaid, sizeof(ia->iaid), buf, sizeof(buf))); warn_iaid_conflict(ifp, ia->ia_type, ia->iaid); } } #endif } #ifdef INET6 if (ifo->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) { logerr("%s: ipv6_start", ifp->name); ifo->options &= ~DHCPCD_IPV6; } if (ifo->options & DHCPCD_IPV6) { if (ifp->active == IF_ACTIVE_USER) { ipv6_startstatic(ifp); if (ifo->options & DHCPCD_IPV6RS) ipv6nd_startrs(ifp); } #ifdef DHCP6 /* DHCPv6 could be turned off, but the interface * is still delegated to. */ if (ifp->active) dhcp6_find_delegates(ifp); if (ifo->options & DHCPCD_DHCP6) { if (ifp->active == IF_ACTIVE_USER) { enum DH6S d6_state; if (ifo->options & DHCPCD_IA_FORCED) d6_state = DH6S_INIT; else if (ifo->options & DHCPCD_INFORM6) d6_state = DH6S_INFORM; else d6_state = DH6S_CONFIRM; if (dhcp6_start(ifp, d6_state) == -1) logerr("%s: dhcp6_start", ifp->name); } } #endif } #endif #ifdef INET if (ifo->options & DHCPCD_IPV4 && ifp->active == IF_ACTIVE_USER) { /* Ensure we have an IPv4 state before starting DHCP */ if (ipv4_getstate(ifp) != NULL) dhcp_start(ifp); } #endif } static void dhcpcd_prestartinterface(void *arg) { struct interface *ifp = arg; struct dhcpcd_ctx *ctx = ifp->ctx; bool randmac_down; if (ifp->carrier <= LINK_DOWN && ifp->options->randomise_hwaddr && ifp->flags & IFF_UP) { if_down(ifp); randmac_down = true; } else randmac_down = false; if ((!(ctx->options & DHCPCD_MANAGER) || ifp->options->options & DHCPCD_IF_UP || randmac_down) && !(ifp->flags & IFF_UP)) { if (ifp->options->randomise_hwaddr && if_randomisemac(ifp) == -1) logerr(__func__); if (if_up(ifp) == -1) logerr(__func__); } dhcpcd_startinterface(ifp); } static void run_preinit(struct interface *ifp) { if (ifp->ctx->options & DHCPCD_TEST) return; script_runreason(ifp, "PREINIT"); if (ifp->wireless && if_is_link_up(ifp)) dhcpcd_reportssid(ifp); if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN) script_runreason(ifp, ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER"); } void dhcpcd_activateinterface(struct interface *ifp, unsigned long long options) { if (ifp->active) return; ifp->active = IF_ACTIVE; dhcpcd_initstate2(ifp, options); /* It's possible we might not have been able to load * a config. */ if (!ifp->active) return; configure_interface1(ifp); run_preinit(ifp); dhcpcd_prestartinterface(ifp); } int dhcpcd_handleinterface(void *arg, int action, const char *ifname) { struct dhcpcd_ctx *ctx = arg; struct ifaddrs *ifaddrs; struct if_head *ifs; struct interface *ifp, *iff; const char * const argv[] = { ifname }; int e; if (action == -1) { ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL) { errno = ESRCH; return -1; } if (ifp->active) { logdebugx("%s: interface departed", ifp->name); stop_interface(ifp, "DEPARTED"); } TAILQ_REMOVE(ctx->ifaces, ifp, next); if_free(ifp); return 0; } ifs = if_discover(ctx, &ifaddrs, -1, UNCONST(argv)); if (ifs == NULL) { logerr(__func__); return -1; } ifp = if_find(ifs, ifname); if (ifp == NULL) { /* This can happen if an interface is quickly added * and then removed. */ errno = ENOENT; e = -1; goto out; } e = 1; /* Check if we already have the interface */ iff = if_find(ctx->ifaces, ifp->name); if (iff != NULL) { if (iff->active) logdebugx("%s: interface updated", iff->name); /* The flags and hwaddr could have changed */ iff->flags = ifp->flags; iff->hwlen = ifp->hwlen; if (ifp->hwlen != 0) memcpy(iff->hwaddr, ifp->hwaddr, iff->hwlen); } else { TAILQ_REMOVE(ifs, ifp, next); TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); if (ifp->active) { logdebugx("%s: interface added", ifp->name); dhcpcd_initstate(ifp, 0); run_preinit(ifp); } iff = ifp; } if (action > 0) { if_learnaddrs(ctx, ifs, &ifaddrs); if (iff->active) dhcpcd_prestartinterface(iff); } out: /* Free our discovered list */ while ((ifp = TAILQ_FIRST(ifs))) { TAILQ_REMOVE(ifs, ifp, next); if_free(ifp); } free(ifs); return e; } static void dhcpcd_handlelink(void *arg) { struct dhcpcd_ctx *ctx = arg; if (if_handlelink(ctx) == -1) { if (errno == ENOBUFS || errno == ENOMEM) { dhcpcd_linkoverflow(ctx); return; } if (errno != ENOTSUP) logerr(__func__); } } static void dhcpcd_checkcarrier(void *arg) { struct interface *ifp0 = arg, *ifp; ifp = if_find(ifp0->ctx->ifaces, ifp0->name); if (ifp == NULL || ifp->carrier == ifp0->carrier) return; dhcpcd_handlecarrier(ifp, ifp0->carrier, ifp0->flags); if_free(ifp0); } #ifndef SMALL static void dhcpcd_setlinkrcvbuf(struct dhcpcd_ctx *ctx) { socklen_t socklen; if (ctx->link_rcvbuf == 0) return; logdebugx("setting route socket receive buffer size to %d bytes", ctx->link_rcvbuf); socklen = sizeof(ctx->link_rcvbuf); if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RCVBUF, &ctx->link_rcvbuf, socklen) == -1) logerr(__func__); } #endif static void dhcpcd_runprestartinterface(void *arg) { struct interface *ifp = arg; run_preinit(ifp); dhcpcd_prestartinterface(ifp); } void dhcpcd_linkoverflow(struct dhcpcd_ctx *ctx) { socklen_t socklen; int rcvbuflen; char buf[2048]; ssize_t rlen; size_t rcnt; struct if_head *ifaces; struct ifaddrs *ifaddrs; struct interface *ifp, *ifn, *ifp1; socklen = sizeof(rcvbuflen); if (getsockopt(ctx->link_fd, SOL_SOCKET, SO_RCVBUF, &rcvbuflen, &socklen) == -1) { logerr("%s: getsockopt", __func__); rcvbuflen = 0; } #ifdef __linux__ else rcvbuflen /= 2; #endif logerrx("route socket overflowed (rcvbuflen %d)" " - learning interface state", rcvbuflen); /* Drain the socket. * We cannot open a new one due to privsep. */ rcnt = 0; do { rlen = read(ctx->link_fd, buf, sizeof(buf)); if (++rcnt % 1000 == 0) logwarnx("drained %zu messages", rcnt); } while (rlen != -1 || errno == ENOBUFS || errno == ENOMEM); if (rcnt % 1000 != 0) logwarnx("drained %zu messages", rcnt); /* Work out the current interfaces. */ ifaces = if_discover(ctx, &ifaddrs, ctx->ifc, ctx->ifv); if (ifaces == NULL) { logerr(__func__); return; } /* Punt departed interfaces */ TAILQ_FOREACH_SAFE(ifp, ctx->ifaces, next, ifn) { if (if_find(ifaces, ifp->name) != NULL) continue; dhcpcd_handleinterface(ctx, -1, ifp->name); } /* Add new interfaces */ while ((ifp = TAILQ_FIRST(ifaces)) != NULL ) { TAILQ_REMOVE(ifaces, ifp, next); ifp1 = if_find(ctx->ifaces, ifp->name); if (ifp1 != NULL) { /* If the interface already exists, * check carrier state. * dhcpcd_checkcarrier will free ifp. */ eloop_timeout_add_sec(ctx->eloop, 0, dhcpcd_checkcarrier, ifp); continue; } TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); if (ifp->active) { dhcpcd_initstate(ifp, 0); eloop_timeout_add_sec(ctx->eloop, 0, dhcpcd_runprestartinterface, ifp); } } free(ifaces); /* Update address state. */ if_markaddrsstale(ctx->ifaces); if_learnaddrs(ctx, ctx->ifaces, &ifaddrs); if_deletestaleaddrs(ctx->ifaces); } void dhcpcd_handlehwaddr(struct interface *ifp, uint16_t hwtype, const void *hwaddr, uint8_t hwlen) { char buf[sizeof(ifp->hwaddr) * 3]; if (hwaddr == NULL || !if_valid_hwaddr(hwaddr, hwlen)) hwlen = 0; if (hwlen > sizeof(ifp->hwaddr)) { errno = ENOBUFS; logerr("%s: %s", __func__, ifp->name); return; } if (ifp->hwtype != hwtype) { if (ifp->active) loginfox("%s: hardware address type changed" " from %d to %d", ifp->name, ifp->hwtype, hwtype); ifp->hwtype = hwtype; } if (ifp->hwlen == hwlen && (hwlen == 0 || memcmp(ifp->hwaddr, hwaddr, hwlen) == 0)) return; if (ifp->active) { loginfox("%s: old hardware address: %s", ifp->name, hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf))); loginfox("%s: new hardware address: %s", ifp->name, hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf))); } ifp->hwlen = hwlen; if (hwaddr != NULL) memcpy(ifp->hwaddr, hwaddr, hwlen); } static void if_reboot(struct interface *ifp, int argc, char **argv) { #ifdef INET unsigned long long oldopts; oldopts = ifp->options->options; #endif script_runreason(ifp, "RECONFIGURE"); dhcpcd_initstate1(ifp, argc, argv, 0); #ifdef INET dhcp_reboot_newopts(ifp, oldopts); #endif #ifdef DHCP6 dhcp6_reboot(ifp); #endif dhcpcd_prestartinterface(ifp); } static void reload_config(struct dhcpcd_ctx *ctx) { struct if_options *ifo; free_globals(ctx); if ((ifo = read_config(ctx, NULL, NULL, NULL)) == NULL) return; add_options(ctx, NULL, ifo, ctx->argc, ctx->argv); /* We need to preserve these options. */ if (ctx->options & DHCPCD_STARTED) ifo->options |= DHCPCD_STARTED; if (ctx->options & DHCPCD_MANAGER) ifo->options |= DHCPCD_MANAGER; if (ctx->options & DHCPCD_DAEMONISED) ifo->options |= DHCPCD_DAEMONISED; if (ctx->options & DHCPCD_PRIVSEP) ifo->options |= DHCPCD_PRIVSEP; ctx->options = ifo->options; free_options(ctx, ifo); } static void reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi) { int i; struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { for (i = oi; i < argc; i++) { if (strcmp(ifp->name, argv[i]) == 0) break; } if (oi != argc && i == argc) continue; if (ifp->active == IF_ACTIVE_USER) { if (action) if_reboot(ifp, argc, argv); #ifdef INET else ipv4_applyaddr(ifp); #endif } else if (i != argc) { ifp->active = IF_ACTIVE_USER; dhcpcd_initstate1(ifp, argc, argv, 0); run_preinit(ifp); dhcpcd_prestartinterface(ifp); } } } static void stop_all_interfaces(struct dhcpcd_ctx *ctx, unsigned long long opts) { struct interface *ifp; ctx->options |= DHCPCD_EXITING; if (ctx->ifaces == NULL) return; /* Drop the last interface first */ TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) { if (!ifp->active) continue; ifp->options->options |= opts; if (ifp->options->options & DHCPCD_RELEASE) ifp->options->options &= ~DHCPCD_PERSISTENT; ifp->options->options |= DHCPCD_EXITING; stop_interface(ifp, NULL); } } static void dhcpcd_ifrenew(struct interface *ifp) { if (!ifp->active) return; if (ifp->options->options & DHCPCD_LINK && !if_is_link_up(ifp)) return; #ifdef INET dhcp_renew(ifp); #endif #ifdef INET6 #define DHCPCD_RARENEW (DHCPCD_IPV6 | DHCPCD_IPV6RS) if ((ifp->options->options & DHCPCD_RARENEW) == DHCPCD_RARENEW) ipv6nd_startrs(ifp); #endif #ifdef DHCP6 dhcp6_renew(ifp); #endif } static void dhcpcd_renew(struct dhcpcd_ctx *ctx) { struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { dhcpcd_ifrenew(ifp); } } #ifdef USE_SIGNALS #define sigmsg "received %s, %s" static void dhcpcd_signal_cb(int sig, void *arg) { struct dhcpcd_ctx *ctx = arg; unsigned long long opts; int exit_code; if (ctx->options & DHCPCD_DUMPLEASE) { eloop_exit(ctx->eloop, EXIT_FAILURE); return; } if (sig != SIGCHLD && ctx->options & DHCPCD_FORKED) { if (sig != SIGHUP && write(ctx->fork_fd, &sig, sizeof(sig)) == -1) logerr("%s: write", __func__); return; } opts = 0; exit_code = EXIT_FAILURE; switch (sig) { case SIGINT: loginfox(sigmsg, "SIGINT", "stopping"); break; case SIGTERM: loginfox(sigmsg, "SIGTERM", "stopping"); exit_code = EXIT_SUCCESS; break; case SIGALRM: loginfox(sigmsg, "SIGALRM", "releasing"); opts |= DHCPCD_RELEASE; exit_code = EXIT_SUCCESS; break; case SIGHUP: loginfox(sigmsg, "SIGHUP", "rebinding"); reload_config(ctx); /* Preserve any options passed on the commandline * when we were started. */ reconf_reboot(ctx, 1, ctx->argc, ctx->argv, ctx->argc - ctx->ifc); return; case SIGUSR1: loginfox(sigmsg, "SIGUSR1", "renewing"); dhcpcd_renew(ctx); return; case SIGUSR2: loginfox(sigmsg, "SIGUSR2", "reopening log"); #ifdef PRIVSEP if (IN_PRIVSEP(ctx)) { if (ps_root_logreopen(ctx) == -1) logerr("ps_root_logreopen"); return; } #endif if (logopen(ctx->logfile) == -1) logerr("logopen"); return; case SIGCHLD: while (waitpid(-1, NULL, WNOHANG) > 0) ; return; default: logerrx("received signal %d but don't know what to do with it", sig); return; } if (!(ctx->options & DHCPCD_TEST)) stop_all_interfaces(ctx, opts); eloop_exit(ctx->eloop, exit_code); } #endif int dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, char **argv) { struct interface *ifp; unsigned long long opts; int opt, oi, do_reboot, do_renew, af = AF_UNSPEC; size_t len, l, nifaces; char *tmp, *p; /* Special commands for our control socket * as the other end should be blocking until it gets the * expected reply we should be safely able just to change the * write callback on the fd */ /* Make any change here in privsep-control.c as well. */ if (strcmp(*argv, "--version") == 0) { return control_queue(fd, UNCONST(VERSION), strlen(VERSION) + 1); } else if (strcmp(*argv, "--getconfigfile") == 0) { return control_queue(fd, UNCONST(fd->ctx->cffile), strlen(fd->ctx->cffile) + 1); } else if (strcmp(*argv, "--getinterfaces") == 0) { optind = argc = 0; goto dumplease; } else if (strcmp(*argv, "--listen") == 0) { fd->flags |= FD_LISTEN; return 0; } /* Log the command */ len = 1; for (opt = 0; opt < argc; opt++) len += strlen(argv[opt]) + 1; tmp = malloc(len); if (tmp == NULL) return -1; p = tmp; for (opt = 0; opt < argc; opt++) { l = strlen(argv[opt]); strlcpy(p, argv[opt], len); len -= l + 1; p += l; *p++ = ' '; } *--p = '\0'; loginfox("control command: %s", tmp); free(tmp); optind = 0; oi = 0; opts = 0; do_reboot = do_renew = 0; while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) { switch (opt) { case 'g': /* Assumed if below not set */ break; case 'k': opts |= DHCPCD_RELEASE; break; case 'n': do_reboot = 1; break; case 'p': opts |= DHCPCD_PERSISTENT; break; case 'x': opts |= DHCPCD_EXITING; break; case 'N': do_renew = 1; break; case 'U': opts |= DHCPCD_DUMPLEASE; break; case '4': af = AF_INET; break; case '6': af = AF_INET6; break; } } if (opts & DHCPCD_DUMPLEASE) { ctx->options |= DHCPCD_DUMPLEASE; dumplease: nifaces = 0; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; for (oi = optind; oi < argc; oi++) { if (strcmp(ifp->name, argv[oi]) == 0) break; } if (optind == argc || oi < argc) { opt = send_interface(NULL, ifp, af); if (opt == -1) goto dumperr; nifaces += (size_t)opt; } } if (write(fd->fd, &nifaces, sizeof(nifaces)) != sizeof(nifaces)) goto dumperr; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; for (oi = optind; oi < argc; oi++) { if (strcmp(ifp->name, argv[oi]) == 0) break; } if (optind == argc || oi < argc) { if (send_interface(fd, ifp, af) == -1) goto dumperr; } } ctx->options &= ~DHCPCD_DUMPLEASE; return 0; dumperr: ctx->options &= ~DHCPCD_DUMPLEASE; return -1; } /* Only privileged users can control dhcpcd via the socket. */ if (fd->flags & FD_UNPRIV) { errno = EPERM; return -1; } if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) { if (optind == argc) { stop_all_interfaces(ctx, opts); eloop_exit(ctx->eloop, EXIT_SUCCESS); return 0; } for (oi = optind; oi < argc; oi++) { if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) continue; if (!ifp->active) continue; ifp->options->options |= opts; if (opts & DHCPCD_RELEASE) ifp->options->options &= ~DHCPCD_PERSISTENT; stop_interface(ifp, NULL); } return 0; } if (do_renew) { if (optind == argc) { dhcpcd_renew(ctx); return 0; } for (oi = optind; oi < argc; oi++) { if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) continue; dhcpcd_ifrenew(ifp); } return 0; } reload_config(ctx); /* XXX: Respect initial commandline options? */ reconf_reboot(ctx, do_reboot, argc, argv, optind - 1); return 0; } static void dhcpcd_readdump1(void *); static void dhcpcd_readdump2(void *arg) { struct dhcpcd_ctx *ctx = arg; ssize_t len; int exit_code = EXIT_FAILURE; len = read(ctx->control_fd, ctx->ctl_buf + ctx->ctl_bufpos, ctx->ctl_buflen - ctx->ctl_bufpos); if (len == -1) { logerr(__func__); goto finished; } else if (len == 0) goto finished; if ((size_t)len + ctx->ctl_bufpos != ctx->ctl_buflen) { ctx->ctl_bufpos += (size_t)len; return; } if (ctx->ctl_buf[ctx->ctl_buflen - 1] != '\0') /* unlikely */ ctx->ctl_buf[ctx->ctl_buflen - 1] = '\0'; script_dump(ctx->ctl_buf, ctx->ctl_buflen); fflush(stdout); if (--ctx->ctl_extra != 0) { putchar('\n'); eloop_event_add(ctx->eloop, ctx->control_fd, dhcpcd_readdump1, ctx); return; } exit_code = EXIT_SUCCESS; finished: shutdown(ctx->control_fd, SHUT_RDWR); eloop_exit(ctx->eloop, exit_code); } static void dhcpcd_readdump1(void *arg) { struct dhcpcd_ctx *ctx = arg; ssize_t len; len = read(ctx->control_fd, &ctx->ctl_buflen, sizeof(ctx->ctl_buflen)); if (len != sizeof(ctx->ctl_buflen)) { if (len != -1) errno = EINVAL; goto err; } if (ctx->ctl_buflen > SSIZE_MAX) { errno = ENOBUFS; goto err; } free(ctx->ctl_buf); ctx->ctl_buf = malloc(ctx->ctl_buflen); if (ctx->ctl_buf == NULL) goto err; ctx->ctl_bufpos = 0; eloop_event_add(ctx->eloop, ctx->control_fd, dhcpcd_readdump2, ctx); return; err: logerr(__func__); eloop_exit(ctx->eloop, EXIT_FAILURE); } static void dhcpcd_readdump0(void *arg) { struct dhcpcd_ctx *ctx = arg; ssize_t len; len = read(ctx->control_fd, &ctx->ctl_extra, sizeof(ctx->ctl_extra)); if (len != sizeof(ctx->ctl_extra)) { if (len != -1) errno = EINVAL; logerr(__func__); eloop_exit(ctx->eloop, EXIT_FAILURE); return; } if (ctx->ctl_extra == 0) { eloop_exit(ctx->eloop, EXIT_SUCCESS); return; } eloop_event_add(ctx->eloop, ctx->control_fd, dhcpcd_readdump1, ctx); } static void dhcpcd_readdumptimeout(void *arg) { struct dhcpcd_ctx *ctx = arg; logerrx(__func__); eloop_exit(ctx->eloop, EXIT_FAILURE); } static int dhcpcd_readdump(struct dhcpcd_ctx *ctx) { ctx->options |= DHCPCD_FORKED; if (eloop_timeout_add_sec(ctx->eloop, 5, dhcpcd_readdumptimeout, ctx) == -1) return -1; return eloop_event_add(ctx->eloop, ctx->control_fd, dhcpcd_readdump0, ctx); } static void dhcpcd_fork_cb(void *arg) { struct dhcpcd_ctx *ctx = arg; int exit_code; ssize_t len; len = read(ctx->fork_fd, &exit_code, sizeof(exit_code)); if (len == -1) { logerr(__func__); exit_code = EXIT_FAILURE; } else if ((size_t)len < sizeof(exit_code)) { logerrx("%s: truncated read %zd (expected %zu)", __func__, len, sizeof(exit_code)); exit_code = EXIT_FAILURE; } if (ctx->options & DHCPCD_FORKED) eloop_exit(ctx->eloop, exit_code); else dhcpcd_signal_cb(exit_code, ctx); } static void dhcpcd_stderr_cb(void *arg) { struct dhcpcd_ctx *ctx = arg; char log[BUFSIZ]; ssize_t len; len = read(ctx->stderr_fd, log, sizeof(log)); if (len == -1) { if (errno != ECONNRESET) logerr(__func__); return; } log[len] = '\0'; fprintf(stderr, "%s", log); } int main(int argc, char **argv, char **envp) { struct dhcpcd_ctx ctx; struct ifaddrs *ifaddrs = NULL; struct if_options *ifo; struct interface *ifp; sa_family_t family = AF_UNSPEC; int opt, oi = 0, i; unsigned int logopts, t; ssize_t len; #if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) pid_t pid; int fork_fd[2], stderr_fd[2]; #endif #ifdef USE_SIGNALS int sig = 0; const char *siga = NULL; size_t si; #endif #ifdef SETPROCTITLE_H setproctitle_init(argc, argv, envp); #else UNUSED(envp); #endif /* Test for --help and --version */ if (argc > 1) { if (strcmp(argv[1], "--help") == 0) { usage(); return EXIT_SUCCESS; } else if (strcmp(argv[1], "--version") == 0) { printf(""PACKAGE" "VERSION"\n%s\n", dhcpcd_copyright); printf("Compiled in features:" #ifdef INET " INET" #endif #ifdef ARP " ARP" #endif #ifdef ARPING " ARPing" #endif #ifdef IPV4LL " IPv4LL" #endif #ifdef INET6 " INET6" #endif #ifdef DHCP6 " DHCPv6" #endif #ifdef AUTH " AUTH" #endif #ifdef PRIVSEP " PRIVSEP" #endif "\n"); return EXIT_SUCCESS; } } memset(&ctx, 0, sizeof(ctx)); ifo = NULL; ctx.cffile = CONFIG; ctx.script = UNCONST(dhcpcd_default_script); ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1; ctx.pf_inet_fd = -1; #ifdef PF_LINK ctx.pf_link_fd = -1; #endif TAILQ_INIT(&ctx.control_fds); #ifdef USE_SIGNALS ctx.fork_fd = -1; #endif #ifdef PLUGIN_DEV ctx.dev_fd = -1; #endif #ifdef INET ctx.udp_rfd = -1; ctx.udp_wfd = -1; #endif #if defined(INET6) && !defined(__sun) ctx.nd_fd = -1; #endif #ifdef DHCP6 ctx.dhcp6_rfd = -1; ctx.dhcp6_wfd = -1; #endif #ifdef PRIVSEP ctx.ps_root_fd = ctx.ps_log_fd = ctx.ps_data_fd = -1; ctx.ps_inet_fd = ctx.ps_control_fd = -1; TAILQ_INIT(&ctx.ps_processes); #endif /* Check our streams for validity */ ctx.stdin_valid = fcntl(STDIN_FILENO, F_GETFD) != -1; ctx.stdout_valid = fcntl(STDOUT_FILENO, F_GETFD) != -1; ctx.stderr_valid = fcntl(STDERR_FILENO, F_GETFD) != -1; logopts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID; if (ctx.stderr_valid) logopts |= LOGERR_ERR; i = 0; while ((opt = getopt_long(argc, argv, ctx.options & DHCPCD_PRINT_PIDFILE ? NOERR_IF_OPTS : IF_OPTS, cf_options, &oi)) != -1) { switch (opt) { case '4': family = AF_INET; break; case '6': family = AF_INET6; break; case 'f': ctx.cffile = optarg; break; case 'j': free(ctx.logfile); ctx.logfile = strdup(optarg); break; #ifdef USE_SIGNALS case 'k': sig = SIGALRM; siga = "ALRM"; break; case 'n': sig = SIGHUP; siga = "HUP"; break; case 'g': case 'p': /* Force going via command socket as we're * out of user definable signals. */ i = 4; break; case 'q': /* -qq disables console output entirely. * This is important for systemd because it logs * both console AND syslog to the same log * resulting in untold confusion. */ if (logopts & LOGERR_QUIET) logopts &= ~LOGERR_ERR; else logopts |= LOGERR_QUIET; break; case 'x': sig = SIGTERM; siga = "TERM"; break; case 'N': sig = SIGUSR1; siga = "USR1"; break; #endif case 'P': ctx.options |= DHCPCD_PRINT_PIDFILE; logopts &= ~(LOGERR_LOG | LOGERR_ERR); break; case 'T': i = 1; logopts &= ~LOGERR_LOG; break; case 'U': i = 3; break; case 'V': i = 2; break; case '?': if (ctx.options & DHCPCD_PRINT_PIDFILE) continue; usage(); goto exit_failure; } } if (optind != argc - 1) ctx.options |= DHCPCD_MANAGER; logsetopts(logopts); logopen(ctx.logfile); ctx.argv = argv; ctx.argc = argc; ctx.ifc = argc - optind; ctx.ifv = argv + optind; rt_init(&ctx); ifo = read_config(&ctx, NULL, NULL, NULL); if (ifo == NULL) { if (ctx.options & DHCPCD_PRINT_PIDFILE) goto printpidfile; goto exit_failure; } opt = add_options(&ctx, NULL, ifo, argc, argv); if (opt != 1) { if (ctx.options & DHCPCD_PRINT_PIDFILE) goto printpidfile; if (opt == 0) usage(); goto exit_failure; } if (i == 2) { printf("Interface options:\n"); if (optind == argc - 1) { free_options(&ctx, ifo); ifo = read_config(&ctx, argv[optind], NULL, NULL); if (ifo == NULL) goto exit_failure; add_options(&ctx, NULL, ifo, argc, argv); } if_printoptions(); #ifdef INET if (family == 0 || family == AF_INET) { printf("\nDHCPv4 options:\n"); dhcp_printoptions(&ctx, ifo->dhcp_override, ifo->dhcp_override_len); } #endif #ifdef INET6 if (family == 0 || family == AF_INET6) { printf("\nND options:\n"); ipv6nd_printoptions(&ctx, ifo->nd_override, ifo->nd_override_len); #ifdef DHCP6 printf("\nDHCPv6 options:\n"); dhcp6_printoptions(&ctx, ifo->dhcp6_override, ifo->dhcp6_override_len); #endif } #endif goto exit_success; } ctx.options |= ifo->options; if (i == 1 || i == 3) { if (i == 1) ctx.options |= DHCPCD_TEST; else ctx.options |= DHCPCD_DUMPLEASE; ctx.options |= DHCPCD_PERSISTENT; ctx.options &= ~DHCPCD_DAEMONISE; } #ifdef THERE_IS_NO_FORK ctx.options &= ~DHCPCD_DAEMONISE; #endif if (ctx.options & DHCPCD_DEBUG) logsetopts(logopts | LOGERR_DEBUG); if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { printpidfile: /* If we have any other args, we should run as a single dhcpcd * instance for that interface. */ if (optind == argc - 1 && !(ctx.options & DHCPCD_MANAGER)) { const char *per; const char *ifname; ifname = *ctx.ifv; if (ifname == NULL || strlen(ifname) > IF_NAMESIZE) { errno = ifname == NULL ? EINVAL : E2BIG; logerr("%s: ", ifname); goto exit_failure; } /* Allow a dhcpcd interface per address family */ switch(family) { case AF_INET: per = "-4"; break; case AF_INET6: per = "-6"; break; default: per = ""; } snprintf(ctx.pidfile, sizeof(ctx.pidfile), PIDFILE, ifname, per, "."); } else { snprintf(ctx.pidfile, sizeof(ctx.pidfile), PIDFILE, "", "", ""); ctx.options |= DHCPCD_MANAGER; } if (ctx.options & DHCPCD_PRINT_PIDFILE) { printf("%s\n", ctx.pidfile); goto exit_success; } } if (chdir("/") == -1) logerr("%s: chdir: /", __func__); /* Freeing allocated addresses from dumping leases can trigger * eloop removals as well, so init here. */ if ((ctx.eloop = eloop_new()) == NULL) { logerr("%s: eloop_init", __func__); goto exit_failure; } #ifdef USE_SIGNALS for (si = 0; si < dhcpcd_signals_ignore_len; si++) signal(dhcpcd_signals_ignore[si], SIG_IGN); /* Save signal mask, block and redirect signals to our handler */ eloop_signal_set_cb(ctx.eloop, dhcpcd_signals, dhcpcd_signals_len, dhcpcd_signal_cb, &ctx); if (eloop_signal_mask(ctx.eloop, &ctx.sigset) == -1) { logerr("%s: eloop_signal_mask", __func__); goto exit_failure; } if (sig != 0) { pid = pidfile_read(ctx.pidfile); if (pid != 0 && pid != -1) loginfox("sending signal %s to pid %d", siga, pid); if (pid == 0 || pid == -1 || kill(pid, sig) != 0) { if (sig != SIGHUP && sig != SIGUSR1 && errno != EPERM) logerrx(PACKAGE" not running"); if (pid != 0 && pid != -1 && errno != ESRCH) { logerr("kill"); goto exit_failure; } unlink(ctx.pidfile); if (sig != SIGHUP && sig != SIGUSR1) goto exit_failure; } else { struct timespec ts; if (sig == SIGHUP || sig == SIGUSR1) goto exit_success; /* Spin until it exits */ loginfox("waiting for pid %d to exit", pid); ts.tv_sec = 0; ts.tv_nsec = 100000000; /* 10th of a second */ for(i = 0; i < 100; i++) { nanosleep(&ts, NULL); if (pidfile_read(ctx.pidfile) == -1) goto exit_success; } logerrx("pid %d failed to exit", pid); goto exit_failure; } } #endif #ifdef PRIVSEP ps_init(&ctx); #endif #ifndef SMALL if (ctx.options & DHCPCD_DUMPLEASE && ioctl(fileno(stdin), FIONREAD, &i, sizeof(i)) == 0 && i > 0) { ctx.options |= DHCPCD_FORKED; /* pretend child process */ #ifdef PRIVSEP if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1) goto exit_failure; #endif ifp = calloc(1, sizeof(*ifp)); if (ifp == NULL) { logerr(__func__); goto exit_failure; } ifp->ctx = &ctx; ifp->options = ifo; switch (family) { case AF_INET: #ifdef INET if (dhcp_dump(ifp) == -1) goto exit_failure; break; #else logerrx("No DHCP support"); goto exit_failure; #endif case AF_INET6: #ifdef DHCP6 if (dhcp6_dump(ifp) == -1) goto exit_failure; break; #else logerrx("No DHCP6 support"); goto exit_failure; #endif default: logerrx("Family not specified. Please use -4 or -6."); goto exit_failure; } goto exit_success; } #endif /* Test against siga instead of sig to avoid gcc * warning about a bogus potential signed overflow. * The end result will be the same. */ if ((siga == NULL || i == 4 || ctx.ifc != 0) && !(ctx.options & DHCPCD_TEST)) { ctx.options |= DHCPCD_FORKED; /* avoid socket unlink */ if (!(ctx.options & DHCPCD_MANAGER)) ctx.control_fd = control_open(argv[optind], family, ctx.options & DHCPCD_DUMPLEASE); if (!(ctx.options & DHCPCD_MANAGER) && ctx.control_fd == -1) ctx.control_fd = control_open(argv[optind], AF_UNSPEC, ctx.options & DHCPCD_DUMPLEASE); if (ctx.control_fd == -1) ctx.control_fd = control_open(NULL, AF_UNSPEC, ctx.options & DHCPCD_DUMPLEASE); if (ctx.control_fd != -1) { #ifdef PRIVSEP if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1) goto exit_failure; #endif if (!(ctx.options & DHCPCD_DUMPLEASE)) loginfox("sending commands to dhcpcd process"); len = control_send(&ctx, argc, argv); if (len > 0) logdebugx("send OK"); else { logerr("%s: control_send", __func__); goto exit_failure; } if (ctx.options & DHCPCD_DUMPLEASE) { if (dhcpcd_readdump(&ctx) == -1) { logerr("%s: dhcpcd_readdump", __func__); goto exit_failure; } goto run_loop; } goto exit_success; } else { if (errno != ENOENT) logerr("%s: control_open", __func__); if (ctx.options & DHCPCD_DUMPLEASE) { if (errno == ENOENT) logerrx("dhcpcd is not running"); goto exit_failure; } if (errno == EPERM || errno == EACCES) goto exit_failure; } ctx.options &= ~DHCPCD_FORKED; } if (!(ctx.options & DHCPCD_TEST)) { /* Ensure we have the needed directories */ if (mkdir(DBDIR, 0750) == -1 && errno != EEXIST) logerr("%s: mkdir: %s", __func__, DBDIR); if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) logerr("%s: mkdir: %s", __func__, RUNDIR); if ((pid = pidfile_lock(ctx.pidfile)) != 0) { if (pid == -1) logerr("%s: pidfile_lock: %s", __func__, ctx.pidfile); else logerrx(PACKAGE " already running on pid %d (%s)", pid, ctx.pidfile); goto exit_failure; } } loginfox(PACKAGE "-" VERSION " starting"); if (ctx.stdin_valid && freopen(_PATH_DEVNULL, "w", stdin) == NULL) logwarn("freopen stdin"); #if defined(USE_SIGNALS) && !defined(THERE_IS_NO_FORK) if (!(ctx.options & DHCPCD_DAEMONISE)) goto start_manager; if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fork_fd) == -1 || (ctx.stderr_valid && xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, stderr_fd) == -1)) { logerr("socketpair"); goto exit_failure; } switch (pid = fork()) { case -1: logerr("fork"); goto exit_failure; case 0: ctx.fork_fd = fork_fd[1]; close(fork_fd[0]); #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fd(ctx.fork_fd) == -1) { logerr("ps_rights_limit_fdpair"); goto exit_failure; } #endif eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx); /* * Redirect stderr to the stderr socketpair. * Redirect stdout as well. * dhcpcd doesn't output via stdout, but something in * a called script might. */ if (ctx.stderr_valid) { if (dup2(stderr_fd[1], STDERR_FILENO) == -1 || (ctx.stdout_valid && dup2(stderr_fd[1], STDOUT_FILENO) == -1)) logerr("dup2"); close(stderr_fd[0]); close(stderr_fd[1]); } else if (ctx.stdout_valid) { if (freopen(_PATH_DEVNULL, "w", stdout) == NULL) logerr("freopen stdout"); } if (setsid() == -1) { logerr("%s: setsid", __func__); goto exit_failure; } /* Ensure we can never get a controlling terminal */ switch (pid = fork()) { case -1: logerr("fork"); goto exit_failure; case 0: break; default: ctx.options |= DHCPCD_FORKED; /* A lie */ i = EXIT_SUCCESS; goto exit1; } break; default: setproctitle("[launcher]"); ctx.options |= DHCPCD_FORKED | DHCPCD_LAUNCHER; ctx.fork_fd = fork_fd[0]; close(fork_fd[1]); #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fd(ctx.fork_fd) == -1) { logerr("ps_rights_limit_fd"); goto exit_failure; } #endif eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx); if (ctx.stderr_valid) { ctx.stderr_fd = stderr_fd[0]; close(stderr_fd[1]); #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fd(ctx.stderr_fd) == 1) { logerr("ps_rights_limit_fd"); goto exit_failure; } #endif eloop_event_add(ctx.eloop, ctx.stderr_fd, dhcpcd_stderr_cb, &ctx); } #ifdef PRIVSEP if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1) goto exit_failure; #endif goto run_loop; } /* We have now forked, setsid, forked once more. * From this point on, we are the controlling daemon. */ logdebugx("spawned manager process on PID %d", getpid()); start_manager: ctx.options |= DHCPCD_STARTED; if ((pid = pidfile_lock(ctx.pidfile)) != 0) { logerr("%s: pidfile_lock %d", __func__, pid); #ifdef PRIVSEP /* privsep has not started ... */ ctx.options &= ~DHCPCD_PRIVSEP; #endif goto exit_failure; } #endif os_init(); #if defined(BSD) && defined(INET6) /* Disable the kernel RTADV sysctl as early as possible. */ if (ctx.options & DHCPCD_IPV6 && ctx.options & DHCPCD_IPV6RS) if_disable_rtadv(); #endif #ifdef PRIVSEP if (IN_PRIVSEP(&ctx) && ps_start(&ctx) == -1) { logerr("ps_start"); goto exit_failure; } if (ctx.options & DHCPCD_FORKED) goto run_loop; #endif if (!(ctx.options & DHCPCD_TEST)) { if (control_start(&ctx, ctx.options & DHCPCD_MANAGER ? NULL : argv[optind], family) == -1) { logerr("%s: control_start", __func__); goto exit_failure; } } #ifdef PLUGIN_DEV /* Start any dev listening plugin which may want to * change the interface name provided by the kernel */ if (!IN_PRIVSEP(&ctx) && (ctx.options & (DHCPCD_MANAGER | DHCPCD_DEV)) == (DHCPCD_MANAGER | DHCPCD_DEV)) dev_start(&ctx, dhcpcd_handleinterface); #endif setproctitle("%s%s%s", ctx.options & DHCPCD_MANAGER ? "[manager]" : argv[optind], ctx.options & DHCPCD_IPV4 ? " [ip4]" : "", ctx.options & DHCPCD_IPV6 ? " [ip6]" : ""); if (if_opensockets(&ctx) == -1) { logerr("%s: if_opensockets", __func__); goto exit_failure; } #ifndef SMALL dhcpcd_setlinkrcvbuf(&ctx); #endif /* Try and create DUID from the machine UUID. */ dhcpcd_initduid(&ctx, NULL); /* Cache the default vendor option. */ if (dhcp_vendor(ctx.vendor, sizeof(ctx.vendor)) == -1) logerr("dhcp_vendor"); /* Start handling kernel messages for interfaces, addresses and * routes. */ eloop_event_add(ctx.eloop, ctx.link_fd, dhcpcd_handlelink, &ctx); #ifdef PRIVSEP if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, "stdio route") == -1) goto exit_failure; #endif /* When running dhcpcd against a single interface, we need to retain * the old behaviour of waiting for an IP address */ if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND)) ctx.options |= DHCPCD_WAITIP; ctx.ifaces = if_discover(&ctx, &ifaddrs, ctx.ifc, ctx.ifv); if (ctx.ifaces == NULL) { logerr("%s: if_discover", __func__); goto exit_failure; } for (i = 0; i < ctx.ifc; i++) { if ((ifp = if_find(ctx.ifaces, ctx.ifv[i])) == NULL) logerrx("%s: interface not found", ctx.ifv[i]); else if (!ifp->active) logerrx("%s: interface has an invalid configuration", ctx.ifv[i]); } TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active == IF_ACTIVE_USER) break; } if (ifp == NULL) { if (ctx.ifc == 0) { int loglevel; loglevel = ctx.options & DHCPCD_INACTIVE ? LOG_DEBUG : LOG_ERR; logmessage(loglevel, "no valid interfaces found"); dhcpcd_daemonise(&ctx); } else goto exit_failure; if (!(ctx.options & DHCPCD_LINK)) { logerrx("aborting as link detection is disabled"); goto exit_failure; } } TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) dhcpcd_initstate1(ifp, argc, argv, 0); } if_learnaddrs(&ctx, ctx.ifaces, &ifaddrs); if (ctx.options & DHCPCD_BACKGROUND) dhcpcd_daemonise(&ctx); opt = 0; TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) { run_preinit(ifp); if (if_is_link_up(ifp)) opt = 1; } } if (!(ctx.options & DHCPCD_BACKGROUND)) { if (ctx.options & DHCPCD_MANAGER) t = ifo->timeout; else { t = 0; TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) { t = ifp->options->timeout; break; } } } if (opt == 0 && ctx.options & DHCPCD_LINK && !(ctx.options & DHCPCD_WAITIP)) { int loglevel; loglevel = ctx.options & DHCPCD_INACTIVE ? LOG_DEBUG : LOG_WARNING; logmessage(loglevel, "no interfaces have a carrier"); dhcpcd_daemonise(&ctx); } else if (t > 0 && /* Test mode removes the daemonise bit, so check for both */ ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST)) { eloop_timeout_add_sec(ctx.eloop, t, handle_exit_timeout, &ctx); } } free_options(&ctx, ifo); ifo = NULL; TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) eloop_timeout_add_sec(ctx.eloop, 0, dhcpcd_prestartinterface, ifp); } run_loop: i = eloop_start(ctx.eloop, &ctx.sigset); if (i < 0) { logerr("%s: eloop_start", __func__); goto exit_failure; } goto exit1; exit_success: i = EXIT_SUCCESS; goto exit1; exit_failure: i = EXIT_FAILURE; exit1: if (!(ctx.options & DHCPCD_TEST) && control_stop(&ctx) == -1) logerr("%s: control_stop", __func__); if (ifaddrs != NULL) { #ifdef PRIVSEP_GETIFADDRS if (IN_PRIVSEP(&ctx)) free(ifaddrs); else #endif freeifaddrs(ifaddrs); } /* ps_stop will clear DHCPCD_PRIVSEP but we need to * remember it to avoid attemping to remove the pidfile */ oi = ctx.options & DHCPCD_PRIVSEP ? 1 : 0; #ifdef PRIVSEP ps_stop(&ctx); #endif /* Free memory and close fd's */ if (ctx.ifaces) { while ((ifp = TAILQ_FIRST(ctx.ifaces))) { TAILQ_REMOVE(ctx.ifaces, ifp, next); if_free(ifp); } free(ctx.ifaces); ctx.ifaces = NULL; } free_options(&ctx, ifo); #ifdef HAVE_OPEN_MEMSTREAM if (ctx.script_fp) fclose(ctx.script_fp); #endif free(ctx.script_buf); free(ctx.script_env); rt_dispose(&ctx); free(ctx.duid); if (ctx.link_fd != -1) { eloop_event_delete(ctx.eloop, ctx.link_fd); close(ctx.link_fd); } if_closesockets(&ctx); free_globals(&ctx); #ifdef INET6 ipv6_ctxfree(&ctx); #endif #ifdef PLUGIN_DEV dev_stop(&ctx); #endif #ifdef PRIVSEP eloop_free(ctx.ps_eloop); #endif eloop_free(ctx.eloop); if (ctx.script != dhcpcd_default_script) free(ctx.script); if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) loginfox(PACKAGE " exited"); logclose(); free(ctx.logfile); free(ctx.ctl_buf); #ifdef SETPROCTITLE_H setproctitle_fini(); #endif #ifdef USE_SIGNALS if (ctx.options & DHCPCD_STARTED) { /* Try to detach from the launch process. */ if (ctx.fork_fd != -1 && write(ctx.fork_fd, &i, sizeof(i)) == -1) logerr("%s: write", __func__); } if (ctx.options & DHCPCD_FORKED || oi != 0) _exit(i); /* so atexit won't remove our pidfile */ #endif return i; }