Mercurial > hg > dhcpcd
view src/dhcpcd.c @ 5441:ff7c7b4799b3 draft
dhcpcd: Redirect stdout/stderr to the launcher stderr descriptor
This actually make life really simple!
We no longer need to redirect stdout/stderr to /dev/null for privsep
and any script output is now captured again - and it all goes to stderr
as it should even if a script wants it to go to stdout.
On the happy path, only the master process will actually log anything
to stderr so we turn that off after we "fork".
On the unhappy path, logging to stderr/stdout *may* fail because
the launcher process *may* have exited.
We *could* have the master process as an intermediary but that's
just excess code to avoid errors which *should* not happen.
Regardless, any errror should still hit syslog.
| author | Roy Marples <roy@marples.name> |
|---|---|
| date | Sun, 06 Sep 2020 02:41:08 +0100 |
| parents | 248013138b09 |
| children | a069d919d44c |
line wrap: on
line source
/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2020 Roy Marples <roy@marples.name> * 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. */ const char dhcpcd_copyright[] = "Copyright (c) 2006-2020 Roy Marples"; #include <sys/file.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/types.h> #include <sys/uio.h> #include <sys/wait.h> #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <limits.h> #include <paths.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include <unistd.h> #include <time.h> #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 <sys/capsicum.h> #endif #ifdef HAVE_UTIL_H #include <util.h> #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 #define IF_UPANDRUNNING(a) \ (((a)->flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) 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_MASTER)) { 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)) (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 master 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 master 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) { struct dhcpcd_ctx *ctx; ctx = ifp->ctx; loginfox("%s: removing interface", ifp->name); ifp->options->options |= DHCPCD_STOPPING; dhcpcd_drop(ifp, 1); if (ifp->options->options & DHCPCD_DEPARTED) script_runreason(ifp, "DEPARTED"); else script_runreason(ifp, "STOPPED"); /* 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; /* Set the link state to unknown as we're no longer tracking it. */ ifp->carrier = LINK_UNKNOWN; if (!(ctx->options & (DHCPCD_MASTER | 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); } void dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags, const char *ifname) { struct interface *ifp; ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL || ifp->options == NULL || !(ifp->options->options & DHCPCD_LINK) || !ifp->active) return; if (carrier == LINK_UNKNOWN) { if (ifp->wireless) { carrier = LINK_DOWN; ifp->flags = flags; } else carrier = if_carrier(ifp); } else ifp->flags = flags; if (carrier == LINK_UNKNOWN) carrier = IF_UPANDRUNNING(ifp) ? LINK_UP : LINK_DOWN; if (carrier == LINK_DOWN || (ifp->flags & IFF_UP) == 0) { if (ifp->carrier != LINK_DOWN) { if (ifp->carrier == LINK_UP) loginfox("%s: carrier lost", ifp->name); #ifdef NOCARRIER_PRESERVE_IP if (ifp->flags & IFF_UP && !(ifp->options->options & DHCPCD_ANONYMOUS)) ifp->carrier = LINK_DOWN_IFFUP; else #endif ifp->carrier = LINK_DOWN; script_runreason(ifp, "NOCARRIER"); #ifdef NOCARRIER_PRESERVE_IP if (ifp->flags & IFF_UP && !(ifp->options->options & DHCPCD_ANONYMOUS)) { #ifdef ARP arp_drop(ifp); #endif #ifdef INET dhcp_abort(ifp); #endif #ifdef DHCP6 dhcp6_abort(ifp); #endif } else #endif dhcpcd_drop(ifp, 0); if (ifp->options->options & DHCPCD_ANONYMOUS) { bool was_up = ifp->flags & IFF_UP; if (was_up) if_down(ifp); if (if_randomisemac(ifp) == -1 && errno != ENXIO) logerr(__func__); if (was_up) if_up(ifp); } } } else if (carrier == LINK_UP && ifp->flags & IFF_UP) { if (ifp->carrier != LINK_UP) { loginfox("%s: carrier acquired", ifp->name); ifp->carrier = LINK_UP; #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(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)) { dhcpcd_reportssid(ifp); #ifdef NOCARRIER_PRESERVE_IP dhcpcd_drop(ifp, 0); #endif #ifdef IPV4LL ipv4ll_reset(ifp); #endif } } dhcpcd_initstate(ifp, 0); script_runreason(ifp, "CARRIER"); #ifdef INET6 #ifdef NOCARRIER_PRESERVE_IP /* Set any IPv6 Routers we remembered to expire * faster than they would normally as we * maybe on a new network. */ ipv6nd_startexpire(ifp); #endif #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) return; duid_init(ctx, ifp); if (ctx->duid == NULL) return; 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) { switch (ifp->carrier) { case LINK_UP: break; case LINK_DOWN: loginfox("%s: waiting for carrier", ifp->name); return; case LINK_UNKNOWN: /* No media state available. * Loop until both IFF_UP and IFF_RUNNING are set */ if (ifo->poll == 0) if_pollinit(ifp); 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 anondown; if (ifp->carrier == LINK_DOWN && ifp->options->options & DHCPCD_ANONYMOUS && ifp->flags & IFF_UP) { if_down(ifp); anondown = true; } else anondown = false; if ((!(ctx->options & DHCPCD_MASTER) || ifp->options->options & DHCPCD_IF_UP || anondown) && !(ifp->flags & IFF_UP)) { if (ifp->options->options & DHCPCD_ANONYMOUS && if_randomisemac(ifp) == -1) logerr(__func__); if (if_up(ifp) == -1) logerr(__func__); } if (ifp->options->poll != 0) if_pollinit(ifp); dhcpcd_startinterface(ifp); } static void run_preinit(struct interface *ifp) { if (ifp->ctx->options & DHCPCD_TEST) return; script_runreason(ifp, "PREINIT"); if (ifp->wireless && ifp->carrier == LINK_UP) 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) { ifp->active = IF_ACTIVE; dhcpcd_initstate2(ifp, options); /* It's possible we might not have been able to load * a config. */ if (ifp->active) { configure_interface1(ifp); run_preinit(ifp); dhcpcd_prestartinterface(ifp); } } } int dhcpcd_handleinterface(void *arg, int action, const char *ifname) { struct dhcpcd_ctx *ctx; struct ifaddrs *ifaddrs; struct if_head *ifs; struct interface *ifp, *iff; const char * const argv[] = { ifname }; int e; ctx = arg; 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); ifp->options->options |= DHCPCD_DEPARTED; stop_interface(ifp); } 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 *ifp = arg; int carrier; /* Check carrier here rather than setting LINK_UNKNOWN. * This is because we force LINK_UNKNOWN as down for wireless which * we do not want when dealing with a route socket overflow. */ carrier = if_carrier(ifp); dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name); } #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) 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. */ eloop_timeout_add_sec(ctx->eloop, 0, dhcpcd_checkcarrier, ifp1); if_free(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) { 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; 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_MASTER) ifo->options |= DHCPCD_MASTER; 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); } } static void dhcpcd_ifrenew(struct interface *ifp) { if (!ifp->active) return; if (ifp->options->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) 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) return; pid_t pid = pidfile_read(ctx->pidfile); if (pid == -1) { if (errno != ENOENT) logerr("%s: pidfile_read",__func__); } else if (pid == 0) logerr("%s: pid cannot be zero", __func__); else if (kill(pid, sig) == -1) logerr("%s: kill", __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"); /* XXX This may not work that well in a chroot */ logclose(); if (logopen(ctx->logfile) == -1) logerr(__func__); 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); } 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; } eloop_exit(ctx->eloop, exit_code); } 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) { logerr(__func__); return; } log[len] = '\0'; fprintf(stderr, "%s", log); } int main(int argc, char **argv) { 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 /* 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_data_fd = -1; ctx.ps_inet_fd = ctx.ps_control_fd = -1; TAILQ_INIT(&ctx.ps_processes); #endif rt_init(&ctx); logopts = LOGERR_ERR|LOGERR_LOG|LOGERR_LOG_DATE|LOGERR_LOG_PID; 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; } } logsetopts(logopts); logopen(ctx.logfile); ctx.argv = argv; ctx.argc = argc; ctx.ifc = argc - optind; ctx.ifv = argv + optind; 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_MASTER)) { 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_MASTER; } 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 #ifndef SMALL if (ctx.options & DHCPCD_DUMPLEASE && ioctl(fileno(stdin), FIONREAD, &i, sizeof(i)) == 0 && i > 0) { 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_MASTER)) ctx.control_fd = control_open(argv[optind], family, 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) { 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 (freopen(_PATH_DEVNULL, "r", stdin) == NULL) logerr("%s: freopen stdin", __func__); #ifdef PRIVSEP ps_init(&ctx); #endif #if defined(USE_SIGNALS) && !defined(THERE_IS_NO_FORK) if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fork_fd) == -1 || 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(fork_fd[1]) == -1 || ps_rights_limit_fd(stderr_fd[1]) == 1) { logerr("ps_rights_limit_fdpair"); goto exit_failure; } #endif /* Redirect stderr to the stderr socketpair. * Redirect stdout as well. * dhcpcd doesn't output via stdout, but something in * a called script might. */ if (dup2(stderr_fd[1], STDERR_FILENO) == -1 || dup2(stderr_fd[1], STDOUT_FILENO) == -1) logerr("dup2"); close(stderr_fd[0]); close(stderr_fd[1]); 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: ctx.options |= DHCPCD_FORKED; /* A lie */ ctx.fork_fd = fork_fd[0]; close(fork_fd[1]); ctx.stderr_fd = stderr_fd[0]; close(stderr_fd[1]); #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fd(fork_fd[0]) == -1 || ps_rights_limit_fd(stderr_fd[0]) == 1) { logerr("ps_rights_limit_fdpair"); goto exit_failure; } #endif setproctitle("[launcher]"); eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx); eloop_event_add(ctx.eloop, ctx.stderr_fd, dhcpcd_stderr_cb, &ctx); goto run_loop; } /* We have now forked, setsid, forked once more. * From this point on, we are the controlling daemon. */ ctx.options |= DHCPCD_STARTED; if ((pid = pidfile_lock(ctx.pidfile)) != 0) { logerr("%s: pidfile_lock %d", __func__, pid); goto exit_failure; } #endif #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_MASTER ? 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_MASTER | DHCPCD_DEV)) == (DHCPCD_MASTER | DHCPCD_DEV)) dev_start(&ctx, dhcpcd_handleinterface); #endif setproctitle("%s%s%s", ctx.options & DHCPCD_MASTER ? "[master]" : 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_mastersandbox(&ctx) == -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 (!(ifp->options->options & DHCPCD_LINK) || ifp->carrier != LINK_DOWN) opt = 1; } } if (!(ctx.options & DHCPCD_BACKGROUND)) { if (ctx.options & DHCPCD_MASTER) 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 (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); } #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_free(); #endif #ifdef USE_SIGNALS if (ctx.options & DHCPCD_FORKED) _exit(i); /* so atexit won't remove our pidfile */ else 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__); } #endif return i; }
