diff options
author | Ben Woods <woodsb02@FreeBSD.org> | 2022-08-29 07:04:52 +0000 |
---|---|---|
committer | Ben Woods <woodsb02@FreeBSD.org> | 2022-08-29 07:27:36 +0000 |
commit | 96dba636abec6d5451820add99300bda2ca6d86a (patch) | |
tree | 4ed81d7e264f23333e2f3943b849aa007ec484fa | |
download | src-96dba636abec6d5451820add99300bda2ca6d86a.tar.gz src-96dba636abec6d5451820add99300bda2ca6d86a.zip |
Import dhcpcd 9.4.1vendor/dhcpcd/9.4.1vendor/dhcpcd
Approved by: philip
Differential Revision: https://reviews.freebsd.org/D36196
139 files changed, 55129 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..6b1a56329f4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Ignore configure generated files +config.h +config.mk +config.log + +# Ignore object files +.depend +*.o +*.So +*.so +dhcpcd + +# Ignore generated embedded files +dhcpcd-embedded.c +dhcpcd-embedded.h + +# Ignore generated man pages and scripts +dhcpcd.8 +dhcpcd-run-hooks +dhcpcd-run-hooks.8 +dhcpcd.conf.5 +hooks/30-hostname +hooks/50-ypbind + +# Ignore distribution +dhcpcd*.xz* + +# Ignore patch files +*.diff +*.patch +*.orig +*.rej + +# Ignore swap files +*.swp + +# Ignore Coverity +cov-int diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 000000000000..8b4e1ceae092 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,158 @@ +# Building dhcpcd + +This attempts to document various ways of building dhcpcd for your +platform. + +## Size is an issue +To compile small dhcpcd, maybe to be used for installation media where +size is a concern, you can use the `--small` configure option to enable +a reduced feature set within dhcpcd. +Currently this just removes non important options out of +`dhcpcd-definitions.conf`, the logfile option, +DHCPv6 Prefix Delegation and IPv6 address announcement *(to prefer an +address on another interface)*. +Other features maybe dropped as and when required. +dhcpcd can also be made smaller by removing the IPv4 or IPv6 stack: + * `--disable-inet` + * `--disable-inet6` + +Or by removing the following features: + * `--disable-auth` + * `--disable-arp` + * `--disable-arping` + * `--disable-ipv4ll` + * `--disable-dhcp6` + * `--disable-privsep` + +You can also move the embedded extended configuration from the dhcpcd binary +to an external file (LIBEXECDIR/dhcpcd-definitions.conf) + * `--disable-embedded` +If dhcpcd cannot load this file at runtime, dhcpcd will work but will not be +able to decode any DHCP/DHCPv6 options that are not defined by the user +in /etc/dhcpcd.conf. This does not really change the total on disk size. + +## Cross compiling +If you're cross compiling you may need set the platform if OS is different +from the host. +`--target=sparc-sun-netbsd5.0` + +If you're building for an MMU-less system where fork() does not work, you +should `./configure --disable-fork`. +This also puts the `--no-background` flag on and stops the `--background` flag +from working. + +## Default directories +You can change the default dirs with these knobs. +For example, to satisfy FHS compliance you would do this: +`./configure --libexecdir=/lib/dhcpcd dbdir=/var/lib/dhcpcd` + +## Compile Issues +We now default to using `-std=c99`. For 64-bit linux, this always works, but +for 32-bit linux it requires either gnu99 or a patch to `asm/types.h`. +Most distros patch linux headers so this should work fine. +linux-2.6.24 finally ships with a working 32-bit header. +If your linux headers are older, or your distro hasn't patched them you can +set `CSTD=gnu99` to work around this. + +ArchLinux presently sanitises all kernel headers to the latest version +regardless of the version for your CPU. As such, Arch presently ships a +3.12 kernel with 3.17 headers which claim that it supports temporary address +management and no automatic prefix route generation, both of which are +obviously false. You will have to patch support either in the kernel or +out of the headers (or dhcpcd itself) to have correct operation. + +Linux netlink headers cause a sign conversion error. +I [submitted a patch](https://lkml.org/lkml/2019/12/17/680), +but as yet it's not upstreamed. + +GLIBC ships an icmp6.h header which will result in signedness warnings. +Their [bug #22489](https://sourceware.org/bugzilla/show_bug.cgi?id=22489) +will solve this once it's actually applied. + +## OS specific issues +Some BSD systems do not allow the manipulation of automatically added subnet +routes. You can find discussion here: + http://mail-index.netbsd.org/tech-net/2008/12/03/msg000896.html +BSD systems where this has been fixed or is known to work are: + NetBSD-5.0 + FreeBSD-10.0 + +Some BSD systems protect against IPv6 NS/NA messages by ensuring that the +source address matches a prefix on the recieved by a RA message. +This is an error as the correct check is for on-link prefixes as the +kernel may not be handling RA itself. +BSD systems where this has been fixed or is known to work are: + NetBSD-7.0 + OpenBSD-5.0 + patch submitted against FreeBSD-10.0 + +Some BSD systems do not announce IPv6 address flag changes, such as +`IN6_IFF_TENTATIVE`, `IN6_IFF_DUPLICATED`, etc. On these systems, +dhcpcd will poll a freshly added address until either `IN6_IFF_TENTATIVE` is +cleared or `IN6_IFF_DUPLICATED` is set and take action accordingly. +BSD systems where this has been fixed or is known to work are: + NetBSD-7.0 + +OpenBSD will always add it's own link-local address if no link-local address +exists, because it doesn't check if the address we are adding is a link-local +address or not. + +Some BSD systems do not announce cached neighbour route changes based +on reachability to userland. For such systems, IPv6 routers will always +be assumed to be reachable until they either stop being a router or expire. +BSD systems where this has been fixed or is known to work are: + NetBSD-7.99.3 + +Linux prior to 3.17 won't allow userland to manage IPv6 temporary addresses. +Either upgrade or don't allow dhcpcd to manage the RA, +so don't set either `ipv6ra_own` or `slaac private` in `dhcpcd.conf` if you +want to have working IPv6 temporary addresses. +SLAAC private addresses are just as private, just stable. + +## Init systems +We try and detect how dhcpcd should interact with system services at runtime. +If we cannot auto-detect how do to this, or it is wrong then +you can change this by passing shell commands to `--serviceexists`, +`--servicecmd` and optionally `--servicestatus` to `./configure` or overriding +the service variables in a hook. + + +## /dev management +Some systems have `/dev` management systems and some of these like to rename +interfaces. As this system would listen in the same way as dhcpcd to new +interface arrivals, dhcpcd needs to listen to the `/dev` management sytem +instead of the kernel. However, if the `/dev` management system breaks, stops +working, or changes to a new one, dhcpcd should still try and continue to work. +To facilitate this, dhcpcd allows a plugin to load to instruct dhcpcd when it +can use an interface. As of the time of writing only udev support is included. +You can disable this with `--without-dev`, or `without-udev`. +NOTE: in Gentoo at least, `sys-fs/udev` as provided by systemd leaks memory +`sys-fs/eudev`, the fork of udev does not and as such is recommended. + + +## Importing into another source control system +To import the full sources, use the import target. +To import only the needed sources and documentation, use the import-src +target. +Both targets support DESTDIR to set the installation directory, +if unset it defaults to `/tmp/dhcpcd-$VERSION` +Example: `make DESTDIR=/usr/src/contrib/dhcpcd import-src` + + +## Hooks +Not all the hooks in dhcpcd-hooks are installed by default. +By default we install `01-test`, `20-resolv.conf`and `30-hostname`. +The other hooks, `10-wpa_supplicant`, `15-timezone` and `29-lookup-hostname` +are installed to `$(datadir)/dhcpcd/hooks` by default and need to be +copied to `$(libexecdir)/dhcpcd-hooks` for use. +The configure program attempts to find hooks for systems you have installed. +To add more simply +`./configure -with-hook=ntp.conf` + +If using resolvconf, the `20-resolv.conf` hook now requires a version with the +`-C` and `-c` options to deprecate and activate interfaces to support wireless +roaming (Linux) or carrier just drops (NetBSD). +If your resolvconf does not support this then you will see a warning +about an illegal option when the carrier changes, but things should still work. +In this instance the DNS information cannot be Deprecated and may not +be optimal for multi-homed hosts. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..aba8b6aa6bdf --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2006-2021 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. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000000..53f61063476b --- /dev/null +++ b/Makefile @@ -0,0 +1,108 @@ +SUBDIRS= src hooks + +VERSION!= sed -n 's/\#define VERSION[[:space:]]*"\(.*\)".*/\1/p' src/defs.h + +DIST!= if test -d .git; then echo "dist-git"; \ + else echo "dist-inst"; fi +FOSSILID?= current +GITREF?= HEAD + +DISTSUFFIX= +DISTPREFIX?= dhcpcd-${VERSION}${DISTSUFFIX} +DISTFILEGZ?= ${DISTPREFIX}.tar.gz +DISTFILE?= ${DISTPREFIX}.tar.xz +DISTINFO= ${DISTFILE}.distinfo +DISTINFOSIGN= ${DISTINFO}.asc + +CLEANFILES+= *.tar.xz + +.PHONY: hooks import import-bsd tests + +.SUFFIXES: .in + +all: config.h + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +depend: config.h + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +tests: + cd $@; ${MAKE} $@ + +test: tests + +hooks: + cd $@; ${MAKE} + +eginstall: + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +install: + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +proginstall: + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +clean: + rm -rf cov-int dhcpcd.xz + for x in ${SUBDIRS} tests; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +distclean: clean + rm -f config.h config.mk config.log \ + ${DISTFILE} ${DISTFILEGZ} ${DISTINFO} ${DISTINFOSIGN} + rm -f *.diff *.patch *.orig *.rej + for x in ${SUBDIRS} tests; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +dist-git: + git archive --prefix=${DISTPREFIX}/ ${GITREF} | xz >${DISTFILE} + +dist-inst: + mkdir /tmp/${DISTPREFIX} + cp -RPp * /tmp/${DISTPREFIX} + (cd /tmp/${DISTPREFIX}; make clean) + tar -cvjpf ${DISTFILE} -C /tmp ${DISTPREFIX} + rm -rf /tmp/${DISTPREFIX} + +dist: ${DIST} + +distinfo: dist + rm -f ${DISTINFO} ${DISTINFOSIGN} + ${CKSUM} ${DISTFILE} >${DISTINFO} + #printf "SIZE (${DISTFILE}) = %s\n" $$(wc -c <${DISTFILE}) >>${DISTINFO} + ${PGP} --clearsign --output=${DISTINFOSIGN} ${DISTINFO} + chmod 644 ${DISTINFOSIGN} + ls -l ${DISTFILE} ${DISTINFO} ${DISTINFOSIGN} + +snapshot: + rm -rf /tmp/${DISTPREFIX} + ${INSTALL} -d /tmp/${DISTPREFIX} + cp -RPp * /tmp/${DISTPREFIX} + ${MAKE} -C /tmp/${DISTPREFIX} distclean + tar cf - -C /tmp ${DISTPREFIX} | xz >${DISTFILE} + ls -l ${DISTFILE} + +_import: dist + rm -rf ${DESTDIR}/* + ${INSTALL} -d ${DESTDIR} + tar xvpf ${DISTFILE} -C ${DESTDIR} --strip 1 + @${ECHO} + @${ECHO} "=============================================================" + @${ECHO} "dhcpcd-${VERSION} imported to ${DESTDIR}" + +import: + ${MAKE} _import DESTDIR=`if [ -n "${DESTDIR}" ]; then echo "${DESTDIR}"; else echo /tmp/${DISTPREFIX}; fi` + + +_import-src: clean + rm -rf ${DESTDIR}/* + ${INSTALL} -d ${DESTDIR} + cp LICENSE README.md ${DESTDIR}; + for x in ${SUBDIRS}; do cd $$x; ${MAKE} DESTDIR=${DESTDIR} $@ || exit $$?; cd ..; done + @${ECHO} + @${ECHO} "=============================================================" + @${ECHO} "dhcpcd-${VERSION} imported to ${DESTDIR}" + +import-src: + ${MAKE} _import-src DESTDIR=`if [ -n "${DESTDIR}" ]; then echo "${DESTDIR}"; else echo /tmp/${DISTPREFIX}; fi` + +include Makefile.inc diff --git a/Makefile.inc b/Makefile.inc new file mode 100644 index 000000000000..18b2824dd172 --- /dev/null +++ b/Makefile.inc @@ -0,0 +1,36 @@ +# System definitions + +PICFLAG?= -fPIC + +BINMODE?= 0555 +NONBINMODE?= 0444 +MANMODE?= ${NONBINMODE} +CONFMODE?= 0644 +DBMODE?= 0750 + +CC?= cc +ECHO?= echo +INSTALL?= install +LINT?= lint +SED?= sed +HOST_SH?= /bin/sh + +# This isn't very portable, but I generaly make releases from NetBSD +CKSUM?= cksum -a SHA256 +PGP?= netpgp + +SCRIPT= ${LIBEXECDIR}/dhcpcd-run-hooks +HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks + +SED_RUNDIR= -e 's:@RUNDIR@:${RUNDIR}:g' +SED_DBDIR= -e 's:@DBDIR@:${DBDIR}:g' +SED_LIBDIR= -e 's:@LIBDIR@:${LIBDIR}:g' +SED_DATADIR= -e 's:@DATADIR@:${DATADIR}:g' +SED_HOOKDIR= -e 's:@HOOKDIR@:${HOOKDIR}:g' +SED_SERVICEEXISTS= -e 's:@SERVICEEXISTS@:${SERVICEEXISTS}:g' +SED_SERVICECMD= -e 's:@SERVICECMD@:${SERVICECMD}:g' +SED_SERVICESTATUS= -e 's:@SERVICESTATUS@:${SERVICESTATUS}:g' +SED_STATUSARG= -e 's:@STATUSARG@:${STATUSARG}:g' +SED_SCRIPT= -e 's:@SCRIPT@:${SCRIPT}:g' +SED_SYS= -e 's:@SYSCONFDIR@:${SYSCONFDIR}:g' +SED_DEFAULT_HOSTNAME= -e 's:@DEFAULT_HOSTNAME@:${DEFAULT_HOSTNAME}:g' diff --git a/README.md b/README.md new file mode 100644 index 000000000000..751ea3d440b2 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# dhcpcd + +dhcpcd is a +[DHCP](http://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol) and a +[DHCPv6](http://en.wikipedia.org/wiki/DHCPv6) client. +It's also an IPv4LL (aka [ZeroConf](http://en.wikipedia.org/wiki/Zeroconf)) +client. +In layman's terms, dhcpcd runs on your machine and silently configures your +computer to work on the attached networks without trouble and mostly without +configuration. + +If you're a desktop user then you may also be interested in +[Network Configurator (dhcpcd-ui)](http://roy.marples.name/projects/dhcpcd-ui) +which sits in the notification area and monitors the state of the network via +dhcpcd. +It also has a nice configuration dialog and the ability to enter a pass phrase +for wireless networks. + +dhcpcd may not be the only daemon running that wants to configure DNS on the +host, so it uses [openresolv](http://roy.marples.name/projects/openresolv) +to ensure they can co-exist. + +See [BUILDING.md](BUILDING.md) for how to build dhcpcd. + +## Configuration + +You should read the dhcpcd.conf man page +and put your options into `/etc/dhcpcd.conf`. +The default configuration file should work for most people just fine. +Here it is, in case you lose it. + +``` +# A sample configuration for dhcpcd. +# See dhcpcd.conf(5) for details. + +# Allow users of this group to interact with dhcpcd via the control socket. +#controlgroup wheel + +# Inform the DHCP server of our hostname for DDNS. +hostname + +# Use the hardware address of the interface for the Client ID. +#clientid +# or +# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361. +# Some non-RFC compliant DHCP servers do not reply with this set. +# In this case, comment out duid and enable clientid above. +duid + +# Persist interface configuration when dhcpcd exits. +persistent + +# Rapid commit support. +# Safe to enable by default because it requires the equivalent option set +# on the server to actually work. +option rapid_commit + +# A list of options to request from the DHCP server. +option domain_name_servers, domain_name, domain_search, host_name +option classless_static_routes +# Respect the network MTU. This is applied to DHCP routes. +option interface_mtu + +# Most distributions have NTP support. +#option ntp_servers + +# A ServerID is required by RFC2131. +require dhcp_server_identifier + +# Generate SLAAC address using the Hardware Address of the interface +#slaac hwaddr +# OR generate Stable Private IPv6 Addresses based from the DUID +slaac private +``` + +The dhcpcd man page has a lot of the same options and more, +which only apply to calling dhcpcd from the command line. + + +## Compatibility +dhcpcd-5 is only fully command line compatible with dhcpcd-4 +For compatibility with older versions, use dhcpcd-4 + +## Upgrading +dhcpcd-7 defaults the database directory to `/var/db/dhcpcd` instead of +`/var/db` and now stores dhcpcd.duid and dhcpcd.secret in there instead of +in /etc. + +dhcpcd-9 defaults the run directory to `/var/run/dhcpcd` instead of +`/var/run` and the prefix of dhcpcd has been removed from the files. + +## ChangeLog +We no longer supply a ChangeLog. +However, you're more than welcome to read the +[commit log](https://roy.marples.name/git/dhcpcd/log) and +[archived release announcements](http://roy.marples.name/archives/dhcpcd-discuss/). diff --git a/compat/_strtoi.h b/compat/_strtoi.h new file mode 100644 index 000000000000..fcbd18f9b344 --- /dev/null +++ b/compat/_strtoi.h @@ -0,0 +1,97 @@ +/* $NetBSD: _strtoi.h,v 1.1 2015/01/22 02:15:59 christos Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * Original version ID: + * NetBSD: src/lib/libc/locale/_wcstoul.h,v 1.2 2003/08/07 16:43:03 agc Exp + * + * Created by Kamil Rytarowski, based on ID: + * NetBSD: src/common/lib/libc/stdlib/_strtoul.h,v 1.7 2013/05/17 12:55:56 joerg Exp + */ + +#ifndef _STRTOI_H +#define _STRTOI_H + +/* + * function template for strtoi and strtou + * + * parameters: + * _FUNCNAME : function name + * __TYPE : return and range limits type + * __WRAPPED : wrapped function, strtoimax or strtoumax + */ + +__TYPE +_FUNCNAME(const char * __restrict nptr, char ** __restrict endptr, int base, + __TYPE lo, __TYPE hi, int * rstatus) +{ + int serrno; + __TYPE im; + char *ep; + int rep; + + /* endptr may be NULL */ + + if (endptr == NULL) + endptr = &ep; + + if (rstatus == NULL) + rstatus = &rep; + + serrno = errno; + errno = 0; + + im = __WRAPPED(nptr, endptr, base); + + *rstatus = errno; + errno = serrno; + + if (*rstatus == 0) { + /* No digits were found */ + if (nptr == *endptr) + *rstatus = ECANCELED; + /* There are further characters after number */ + else if (**endptr != '\0') + *rstatus = ENOTSUP; + } + + if (im < lo) { + if (*rstatus == 0) + *rstatus = ERANGE; + return lo; + } + if (im > hi) { + if (*rstatus == 0) + *rstatus = ERANGE; + return hi; + } + + return im; +} +#endif diff --git a/compat/arc4random.c b/compat/arc4random.c new file mode 100644 index 000000000000..90098127c954 --- /dev/null +++ b/compat/arc4random.c @@ -0,0 +1,173 @@ +/* + * Arc4 random number generator for OpenBSD. + * Copyright 1996 David Mazieres <dm@lcs.mit.edu>. + * + * Modification and redistribution in source and binary forms is + * permitted provided that due credit is given to the author and the + * OpenBSD project by leaving this copyright notice intact. + */ + +/* + * This code is derived from section 17.1 of Applied Cryptography, + * second edition, which describes a stream cipher allegedly + * compatible with RSA Labs "RC4" cipher (the actual description of + * which is a trade secret). The same algorithm is used as a stream + * cipher called "arcfour" in Tatu Ylonen's ssh package. + * + * Here the stream cipher has been modified always to include the time + * when initializing the state. That makes it impossible to + * regenerate the same random sequence twice, so this can't be used + * for encryption, but will generate good random numbers. + * + * RC4 is a registered trademark of RSA Laboratories. + */ + +#include <sys/time.h> + +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +#include "arc4random.h" + +struct arc4_stream { + uint8_t i; + uint8_t j; + uint8_t s[256]; + size_t count; + pid_t stir_pid; + int fd; +}; + +#define S(n) (n) +#define S4(n) S(n), S(n + 1), S(n + 2), S(n + 3) +#define S16(n) S4(n), S4(n + 4), S4(n + 8), S4(n + 12) +#define S64(n) S16(n), S16(n + 16), S16(n + 32), S16(n + 48) +#define S256 S64(0), S64(64), S64(128), S64(192) + +static struct arc4_stream rs = { .i = 0xff, .j = 0, .s = { S256 }, + .count = 0, .stir_pid = 0, .fd = -1 }; + +#undef S +#undef S4 +#undef S16 +#undef S64 +#undef S256 + +static void +arc4_addrandom(struct arc4_stream *as, unsigned char *dat, int datlen) +{ + int n; + uint8_t si; + + as->i--; + for (n = 0; n < 256; n++) { + as->i = (uint8_t)(as->i + 1); + si = as->s[as->i]; + as->j = (uint8_t)(as->j + si + dat[n % datlen]); + as->s[as->i] = as->s[as->j]; + as->s[as->j] = si; + } + as->j = as->i; +} + +static uint8_t +arc4_getbyte(struct arc4_stream *as) +{ + uint8_t si, sj; + + as->i = (uint8_t)(as->i + 1); + si = as->s[as->i]; + as->j = (uint8_t)(as->j + si); + sj = as->s[as->j]; + as->s[as->i] = sj; + as->s[as->j] = si; + return (as->s[(si + sj) & 0xff]); +} + +static uint32_t +arc4_getword(struct arc4_stream *as) +{ + int val; + + val = (int)((unsigned int)arc4_getbyte(as) << 24); + val |= arc4_getbyte(as) << 16; + val |= arc4_getbyte(as) << 8; + val |= arc4_getbyte(as); + return (uint32_t)val; +} + +/* We don't care about any error on read, just use what we have + * on the stack. So mask off this GCC warning. */ +#pragma GCC diagnostic ignored "-Wunused-result" +static void +arc4_stir(struct arc4_stream *as) +{ + struct { + struct timeval tv; + unsigned int rnd[(128 - sizeof(struct timeval)) / + sizeof(unsigned int)]; + } rdat; + size_t n; + + gettimeofday(&rdat.tv, NULL); + if (as->fd == -1) { +#ifndef O_CLOEXEC + int fd_opts; +#endif + + as->fd = open("/dev/urandom", O_RDONLY | O_NONBLOCK +#ifdef O_CLOEXEC + | O_CLOEXEC +#endif + ); +#ifndef O_CLOEXEC + if (as->fd != -1 && + (fd_opts = fcntl(as->fd, F_GETFD))) + fcntl(as->fd, F_SETFD, fd_opts | FD_CLOEXEC); +#endif + } + + if (as->fd != -1) { + /* If there is an error reading, just use what is + * on the stack. */ + /* coverity[check_return] */ + (void)read(as->fd, rdat.rnd, sizeof(rdat.rnd)); + } + + /* fd < 0? Ah, what the heck. We'll just take + * whatever was on the stack... */ + /* coverity[uninit_use_in_call] */ + arc4_addrandom(as, (void *) &rdat, sizeof(rdat)); + + /* + * Throw away the first N words of output, as suggested in the + * paper "Weaknesses in the Key Scheduling Algorithm of RC4" + * by Fluher, Mantin, and Shamir. (N = 256 in our case.) + */ + for (n = 0; n < 256 * sizeof(uint32_t); n++) + arc4_getbyte(as); + as->count = 1600000; +} + +static void +arc4_stir_if_needed(struct arc4_stream *as) +{ + pid_t pid; + + pid = getpid(); + if (as->count <= sizeof(uint32_t) || as->stir_pid != pid) { + as->stir_pid = pid; + arc4_stir(as); + } else + as->count -= sizeof(uint32_t); +} + +uint32_t +arc4random() +{ + + arc4_stir_if_needed(&rs); + return arc4_getword(&rs); +} diff --git a/compat/arc4random.h b/compat/arc4random.h new file mode 100644 index 000000000000..a975fef3cd1b --- /dev/null +++ b/compat/arc4random.h @@ -0,0 +1,16 @@ +/* + * Arc4 random number generator for OpenBSD. + * Copyright 1996 David Mazieres <dm@lcs.mit.edu>. + * + * Modification and redistribution in source and binary forms is + * permitted provided that due credit is given to the author and the + * OpenBSD project by leaving this copyright notice intact. + */ + +#ifndef ARC4RANDOM_H +#define ARC4RANDOM_H + +#include <stdint.h> + +uint32_t arc4random(void); +#endif diff --git a/compat/arc4random_uniform.c b/compat/arc4random_uniform.c new file mode 100644 index 000000000000..0f1b7b296759 --- /dev/null +++ b/compat/arc4random_uniform.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdint.h> +#include <stdlib.h> + +/* We need to include config.h so we pickup either the system arc4random + * or our compat one. */ +#include "config.h" + +/* + * Calculate a uniformly distributed random number less than upper_bound + * avoiding "modulo bias". + * + * Uniformity is achieved by generating new random numbers until the one + * returned is outside the range [0, 2**32 % upper_bound). This + * guarantees the selected random number will be inside + * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound) + * after reduction modulo upper_bound. + */ +uint32_t +arc4random_uniform(uint32_t upper_bound) +{ + uint32_t r, min; + + if (upper_bound < 2) + return 0; + + /* 2**32 % x == (2**32 - x) % x */ + min = -upper_bound % upper_bound; + + /* + * This could theoretically loop forever but each retry has + * p > 0.5 (worst case, usually far better) of selecting a + * number inside the range we need, so it should rarely need + * to re-roll. + */ + do + r = arc4random(); + while (r < min); + + return r % upper_bound; +} diff --git a/compat/arc4random_uniform.h b/compat/arc4random_uniform.h new file mode 100644 index 000000000000..d7c0a35d4cba --- /dev/null +++ b/compat/arc4random_uniform.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ARC4RANDOM_UNIFORM_H +#define ARC4RANDOM_UNIFORM_H + +#include <stdint.h> + +uint32_t arc4random_uniform(uint32_t); +#endif diff --git a/compat/bitops.h b/compat/bitops.h new file mode 100644 index 000000000000..31979a20f458 --- /dev/null +++ b/compat/bitops.h @@ -0,0 +1,188 @@ +/* $NetBSD: bitops.h,v 1.11 2012/12/07 02:27:58 christos Exp $ */ + +/*- + * Copyright (c) 2007, 2010 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas and Joerg Sonnenberger. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +#ifndef COMPAT_BITOPS_H +#define COMPAT_BITOPS_H + +#include <stdint.h> +#include "common.h" + +/* + * Find First Set functions + */ +#ifndef ffs32 +static inline int __unused +ffs32(uint32_t _n) +{ + int _v; + + if (!_n) + return 0; + + _v = 1; + if ((_n & 0x0000FFFFU) == 0) { + _n >>= 16; + _v += 16; + } + if ((_n & 0x000000FFU) == 0) { + _n >>= 8; + _v += 8; + } + if ((_n & 0x0000000FU) == 0) { + _n >>= 4; + _v += 4; + } + if ((_n & 0x00000003U) == 0) { + _n >>= 2; + _v += 2; + } + if ((_n & 0x00000001U) == 0) { + //_n >>= 1; + _v += 1; + } + return _v; +} +#endif + +#ifndef ffs64 +static inline int __unused +ffs64(uint64_t _n) +{ + int _v; + + if (!_n) + return 0; + + _v = 1; + if ((_n & 0x00000000FFFFFFFFULL) == 0) { + _n >>= 32; + _v += 32; + } + if ((_n & 0x000000000000FFFFULL) == 0) { + _n >>= 16; + _v += 16; + } + if ((_n & 0x00000000000000FFULL) == 0) { + _n >>= 8; + _v += 8; + } + if ((_n & 0x000000000000000FULL) == 0) { + _n >>= 4; + _v += 4; + } + if ((_n & 0x0000000000000003ULL) == 0) { + _n >>= 2; + _v += 2; + } + if ((_n & 0x0000000000000001ULL) == 0) { + //_n >>= 1; + _v += 1; + } + return _v; +} +#endif + +/* + * Find Last Set functions + */ +#ifndef fls32 +static __inline int __unused +fls32(uint32_t _n) +{ + int _v; + + if (!_n) + return 0; + + _v = 32; + if ((_n & 0xFFFF0000U) == 0) { + _n <<= 16; + _v -= 16; + } + if ((_n & 0xFF000000U) == 0) { + _n <<= 8; + _v -= 8; + } + if ((_n & 0xF0000000U) == 0) { + _n <<= 4; + _v -= 4; + } + if ((_n & 0xC0000000U) == 0) { + _n <<= 2; + _v -= 2; + } + if ((_n & 0x80000000U) == 0) { + //_n <<= 1; + _v -= 1; + } + return _v; +} +#endif + +#ifndef fls64 +static int __unused +fls64(uint64_t _n) +{ + int _v; + + if (!_n) + return 0; + + _v = 64; + if ((_n & 0xFFFFFFFF00000000ULL) == 0) { + _n <<= 32; + _v -= 32; + } + if ((_n & 0xFFFF000000000000ULL) == 0) { + _n <<= 16; + _v -= 16; + } + if ((_n & 0xFF00000000000000ULL) == 0) { + _n <<= 8; + _v -= 8; + } + if ((_n & 0xF000000000000000ULL) == 0) { + _n <<= 4; + _v -= 4; + } + if ((_n & 0xC000000000000000ULL) == 0) { + _n <<= 2; + _v -= 2; + } + if ((_n & 0x8000000000000000ULL) == 0) { + //_n <<= 1; + _v -= 1; + } + return _v; +} +#endif + +#endif /* COMPAT_BITOPS_H_ */ diff --git a/compat/consttime_memequal.h b/compat/consttime_memequal.h new file mode 100644 index 000000000000..983064841075 --- /dev/null +++ b/compat/consttime_memequal.h @@ -0,0 +1,28 @@ +/* + * Written by Matthias Drochner <drochner@NetBSD.org>. + * Public domain. + */ + +#ifndef CONSTTIME_MEMEQUAL_H +#define CONSTTIME_MEMEQUAL_H +inline static int +consttime_memequal(const void *b1, const void *b2, size_t len) +{ + const unsigned char *c1 = b1, *c2 = b2; + unsigned int res = 0; + + while (len--) + res |= *c1++ ^ *c2++; + + /* + * Map 0 to 1 and [1, 256) to 0 using only constant-time + * arithmetic. + * + * This is not simply `!res' because although many CPUs support + * branchless conditional moves and many compilers will take + * advantage of them, certain compilers generate branches on + * certain CPUs for `!res'. + */ + return (1 & ((res - 1) >> 8)); +} +#endif /* CONSTTIME_MEMEQUAL_H */ diff --git a/compat/crypt/hmac.c b/compat/crypt/hmac.c new file mode 100644 index 000000000000..55e331ffa021 --- /dev/null +++ b/compat/crypt/hmac.c @@ -0,0 +1,191 @@ +/* $NetBSD: hmac.c,v 1.5 2017/10/05 09:59:04 roy Exp $ */ + +/*- + * Copyright (c) 2016 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <string.h> +#include <stdlib.h> + +#include "config.h" + +#if defined(HAVE_MD5_H) && !defined(DEPGEN) +#include <md5.h> +#endif + +#ifdef SHA2_H +# include SHA2_H +#endif + +#ifndef __arraycount +#define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) +#endif + +#if 0 +#include <md2.h> +#include <md4.h> +#include <md5.h> +#include <rmd160.h> +#include <sha1.h> +#include <sha2.h> +#endif + +#ifndef MD5_BLOCK_LENGTH +#define MD5_BLOCK_LENGTH 64 +#endif +#ifndef SHA256_BLOCK_LENGTH +#define SHA256_BLOCK_LENGTH 64 +#endif + +#define HMAC_SIZE 128 +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5C + +static const struct hmac { + const char *name; + size_t ctxsize; + size_t digsize; + size_t blocksize; + void (*init)(void *); + void (*update)(void *, const uint8_t *, unsigned int); + void (*final)(uint8_t *, void *); +} hmacs[] = { +#if 0 + { + "md2", sizeof(MD2_CTX), MD2_DIGEST_LENGTH, MD2_BLOCK_LENGTH, + (void *)MD2Init, (void *)MD2Update, (void *)MD2Final, + }, + { + "md4", sizeof(MD4_CTX), MD4_DIGEST_LENGTH, MD4_BLOCK_LENGTH, + (void *)MD4Init, (void *)MD4Update, (void *)MD4Final, + }, +#endif + { + "md5", sizeof(MD5_CTX), MD5_DIGEST_LENGTH, MD5_BLOCK_LENGTH, + (void *)MD5Init, (void *)MD5Update, (void *)MD5Final, + }, +#if 0 + { + "rmd160", sizeof(RMD160_CTX), RMD160_DIGEST_LENGTH, + RMD160_BLOCK_LENGTH, + (void *)RMD160Init, (void *)RMD160Update, (void *)RMD160Final, + }, + { + "sha1", sizeof(SHA1_CTX), SHA1_DIGEST_LENGTH, SHA1_BLOCK_LENGTH, + (void *)SHA1Init, (void *)SHA1Update, (void *)SHA1Final, + }, + { + "sha224", sizeof(SHA224_CTX), SHA224_DIGEST_LENGTH, + SHA224_BLOCK_LENGTH, + (void *)SHA224_Init, (void *)SHA224_Update, + (void *)SHA224_Final, + }, +#endif + { + "sha256", sizeof(SHA256_CTX), SHA256_DIGEST_LENGTH, + SHA256_BLOCK_LENGTH, + (void *)SHA256_Init, (void *)SHA256_Update, + (void *)SHA256_Final, + }, +#if 0 + { + "sha384", sizeof(SHA384_CTX), SHA384_DIGEST_LENGTH, + SHA384_BLOCK_LENGTH, + (void *)SHA384_Init, (void *)SHA384_Update, + (void *)SHA384_Final, + }, + { + "sha512", sizeof(SHA512_CTX), SHA512_DIGEST_LENGTH, + SHA512_BLOCK_LENGTH, + (void *)SHA512_Init, (void *)SHA512_Update, + (void *)SHA512_Final, + }, +#endif +}; + +static const struct hmac * +hmac_find(const char *name) +{ + for (size_t i = 0; i < __arraycount(hmacs); i++) { + if (strcmp(hmacs[i].name, name) != 0) + continue; + return &hmacs[i]; + } + return NULL; +} + +ssize_t +hmac(const char *name, + const void *key, size_t klen, + const void *text, size_t tlen, + void *digest, size_t dlen) +{ + uint8_t ipad[HMAC_SIZE], opad[HMAC_SIZE], d[HMAC_SIZE]; + const uint8_t *k = key; + const struct hmac *h; + uint64_t c[32]; + void *p; + + if ((h = hmac_find(name)) == NULL) + return -1; + + + if (klen > h->blocksize) { + (*h->init)(c); + (*h->update)(c, k, (unsigned int)klen); + (*h->final)(d, c); + k = (void *)d; + klen = h->digsize; + } + + /* Form input and output pads for the digests */ + for (size_t i = 0; i < sizeof(ipad); i++) { + ipad[i] = (i < klen ? k[i] : 0) ^ HMAC_IPAD; + opad[i] = (i < klen ? k[i] : 0) ^ HMAC_OPAD; + } + + p = dlen >= h->digsize ? digest : d; + if (p != digest) { + memcpy(p, digest, dlen); + memset((char *)p + dlen, 0, h->digsize - dlen); + } + (*h->init)(c); + (*h->update)(c, ipad, (unsigned int)h->blocksize); + (*h->update)(c, text, (unsigned int)tlen); + (*h->final)(p, c); + + (*h->init)(c); + (*h->update)(c, opad, (unsigned int)h->blocksize); + (*h->update)(c, digest, (unsigned int)h->digsize); + (*h->final)(p, c); + + if (p != digest) + memcpy(digest, p, dlen); + + return (ssize_t)h->digsize; +} diff --git a/compat/crypt/hmac.h b/compat/crypt/hmac.h new file mode 100644 index 000000000000..af3f77adb025 --- /dev/null +++ b/compat/crypt/hmac.h @@ -0,0 +1,40 @@ +/* $NetBSD: hmac.c,v 1.5 2017/10/05 09:59:04 roy Exp $ */ + +/*- + * Copyright (c) 2016 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +#ifndef HMAC_H +#define HMAC_H + +#include <sys/types.h> + +ssize_t hmac(const char *, const void *, size_t, const void *, size_t, void *, + size_t); + +#endif diff --git a/compat/crypt/md5.c b/compat/crypt/md5.c new file mode 100644 index 000000000000..dea2356de76d --- /dev/null +++ b/compat/crypt/md5.c @@ -0,0 +1,242 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include <sys/param.h> +#include <inttypes.h> + +#include <string.h> + +#include "md5.h" + +#define PUT_64BIT_LE(cp, value) do { \ + (cp)[7] = (uint8_t)((value) >> 56); \ + (cp)[6] = (uint8_t)((value) >> 48); \ + (cp)[5] = (uint8_t)((value) >> 40); \ + (cp)[4] = (uint8_t)((value) >> 32); \ + (cp)[3] = (uint8_t)((value) >> 24); \ + (cp)[2] = (uint8_t)((value) >> 16); \ + (cp)[1] = (uint8_t)((value) >> 8); \ + (cp)[0] = (uint8_t)(value); } while (0) + +#define PUT_32BIT_LE(cp, value) do { \ + (cp)[3] = (uint8_t)((value) >> 24); \ + (cp)[2] = (uint8_t)((value) >> 16); \ + (cp)[1] = (uint8_t)((value) >> 8); \ + (cp)[0] = (uint8_t)(value); } while (0) + +static uint8_t PADDING[MD5_BLOCK_LENGTH] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(MD5_CTX *ctx) +{ + ctx->count = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; +} + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +MD5Transform(uint32_t state[4], const uint8_t block[MD5_BLOCK_LENGTH]) +{ + uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4]; + +#if BYTE_ORDER == LITTLE_ENDIAN + memcpy(in, block, sizeof(in)); +#else + for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) { + in[a] = (uint32_t)( + (uint32_t)(block[a * 4 + 0]) | + (uint32_t)(block[a * 4 + 1]) << 8 | + (uint32_t)(block[a * 4 + 2]) << 16 | + (uint32_t)(block[a * 4 + 3]) << 24); + } +#endif + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(MD5_CTX *ctx, const unsigned char *input, size_t len) +{ + size_t have, need; + + /* Check how many bytes we already have and how many more we need. */ + have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); + need = MD5_BLOCK_LENGTH - have; + + /* Update bitcount */ + ctx->count += (uint64_t)len << 3; + + if (len >= need) { + if (have != 0) { + memcpy(ctx->buffer + have, input, need); + MD5Transform(ctx->state, ctx->buffer); + input += need; + len -= need; + have = 0; + } + + /* Process data in MD5_BLOCK_LENGTH-byte chunks. */ + while (len >= MD5_BLOCK_LENGTH) { + MD5Transform(ctx->state, input); + input += MD5_BLOCK_LENGTH; + len -= MD5_BLOCK_LENGTH; + } + } + + /* Handle any remaining bytes of data. */ + if (len != 0) + memcpy(ctx->buffer + have, input, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx) +{ + uint8_t count[8]; + size_t padlen; + int i; + + /* Convert count to 8 bytes in little endian order. */ + PUT_64BIT_LE(count, ctx->count); + + /* Pad out to 56 mod 64. */ + padlen = MD5_BLOCK_LENGTH - + ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); + if (padlen < 1 + 8) + padlen += MD5_BLOCK_LENGTH; + MD5Update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */ + MD5Update(ctx, count, 8); + + if (digest != NULL) { + for (i = 0; i < 4; i++) + PUT_32BIT_LE(digest + i * 4, ctx->state[i]); + } + memset(ctx, 0, sizeof(*ctx)); /* in case it's sensitive */ +} + + diff --git a/compat/crypt/md5.h b/compat/crypt/md5.h new file mode 100644 index 000000000000..dd156163315c --- /dev/null +++ b/compat/crypt/md5.h @@ -0,0 +1,33 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#ifndef MD5_H_ +#define MD5_H_ + +#define MD5_DIGEST_LENGTH 16 +#define MD5_BLOCK_LENGTH 64ULL + +typedef struct MD5Context { + uint32_t state[4]; /* state (ABCD) */ + uint64_t count; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[MD5_BLOCK_LENGTH]; /* input buffer */ +} MD5_CTX; + +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, const unsigned char *, size_t); +void MD5Final(unsigned char[MD5_DIGEST_LENGTH], MD5_CTX *); +#endif diff --git a/compat/crypt/sha256.c b/compat/crypt/sha256.c new file mode 100644 index 000000000000..73c43c037fa2 --- /dev/null +++ b/compat/crypt/sha256.c @@ -0,0 +1,303 @@ +/*- + * Copyright 2005 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <inttypes.h> + +#include <string.h> + +#ifdef __GLIBC__ +# include <endian.h> +#endif +#ifdef BSD +# ifndef __QNX__ +# include <sys/endian.h> +# endif +#endif + +#include "config.h" +#include "sha256.h" + +#if BYTE_ORDER == BIG_ENDIAN + +/* Copy a vector of big-endian uint32_t into a vector of bytes */ +#define be32enc_vect(dst, src, len) \ + memcpy((void *)dst, (const void *)src, (size_t)len) + +/* Copy a vector of bytes into a vector of big-endian uint32_t */ +#define be32dec_vect(dst, src, len) \ + memcpy((void *)dst, (const void *)src, (size_t)len) + +#else /* BYTE_ORDER != BIG_ENDIAN */ + +/* + * Encode a length len/4 vector of (uint32_t) into a length len vector of + * (unsigned char) in big-endian form. Assumes len is a multiple of 4. + */ +static void +be32enc_vect(unsigned char *dst, const uint32_t *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + be32enc(dst + i * 4, src[i]); +} + +/* + * Decode a big-endian length len vector of (unsigned char) into a length + * len/4 vector of (uint32_t). Assumes len is a multiple of 4. + */ +static void +be32dec_vect(uint32_t *dst, const unsigned char *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + dst[i] = be32dec(src + i * 4); +} + +#endif /* BYTE_ORDER != BIG_ENDIAN */ + +/* Elementary functions used by SHA256 */ +#define Ch(x, y, z) ((x & (y ^ z)) ^ z) +#define Maj(x, y, z) ((x & (y | z)) | (y & z)) +#define SHR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << (32 - n))) +#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +/* SHA256 round function */ +#define RND(a, b, c, d, e, f, g, h, k) \ + t0 = h + S1(e) + Ch(e, f, g) + k; \ + t1 = S0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + +/* Adjusted round function for rotating state */ +#define RNDr(S, W, i, k) \ + RND(S[(64 - i) % 8], S[(65 - i) % 8], \ + S[(66 - i) % 8], S[(67 - i) % 8], \ + S[(68 - i) % 8], S[(69 - i) % 8], \ + S[(70 - i) % 8], S[(71 - i) % 8], \ + W[i] + k) + +/* + * SHA256 block compression function. The 256-bit state is transformed via + * the 512-bit input block to produce a new state. + */ +static void +SHA256_Transform(uint32_t * state, const unsigned char block[64]) +{ + uint32_t W[64]; + uint32_t S[8]; + uint32_t t0, t1; + int i; + + /* 1. Prepare message schedule W. */ + be32dec_vect(W, block, 64); + for (i = 16; i < 64; i++) + W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16]; + + /* 2. Initialize working variables. */ + memcpy(S, state, 32); + + /* 3. Mix. */ + RNDr(S, W, 0, 0x428a2f98); + RNDr(S, W, 1, 0x71374491); + RNDr(S, W, 2, 0xb5c0fbcf); + RNDr(S, W, 3, 0xe9b5dba5); + RNDr(S, W, 4, 0x3956c25b); + RNDr(S, W, 5, 0x59f111f1); + RNDr(S, W, 6, 0x923f82a4); + RNDr(S, W, 7, 0xab1c5ed5); + RNDr(S, W, 8, 0xd807aa98); + RNDr(S, W, 9, 0x12835b01); + RNDr(S, W, 10, 0x243185be); + RNDr(S, W, 11, 0x550c7dc3); + RNDr(S, W, 12, 0x72be5d74); + RNDr(S, W, 13, 0x80deb1fe); + RNDr(S, W, 14, 0x9bdc06a7); + RNDr(S, W, 15, 0xc19bf174); + RNDr(S, W, 16, 0xe49b69c1); + RNDr(S, W, 17, 0xefbe4786); + RNDr(S, W, 18, 0x0fc19dc6); + RNDr(S, W, 19, 0x240ca1cc); + RNDr(S, W, 20, 0x2de92c6f); + RNDr(S, W, 21, 0x4a7484aa); + RNDr(S, W, 22, 0x5cb0a9dc); + RNDr(S, W, 23, 0x76f988da); + RNDr(S, W, 24, 0x983e5152); + RNDr(S, W, 25, 0xa831c66d); + RNDr(S, W, 26, 0xb00327c8); + RNDr(S, W, 27, 0xbf597fc7); + RNDr(S, W, 28, 0xc6e00bf3); + RNDr(S, W, 29, 0xd5a79147); + RNDr(S, W, 30, 0x06ca6351); + RNDr(S, W, 31, 0x14292967); + RNDr(S, W, 32, 0x27b70a85); + RNDr(S, W, 33, 0x2e1b2138); + RNDr(S, W, 34, 0x4d2c6dfc); + RNDr(S, W, 35, 0x53380d13); + RNDr(S, W, 36, 0x650a7354); + RNDr(S, W, 37, 0x766a0abb); + RNDr(S, W, 38, 0x81c2c92e); + RNDr(S, W, 39, 0x92722c85); + RNDr(S, W, 40, 0xa2bfe8a1); + RNDr(S, W, 41, 0xa81a664b); + RNDr(S, W, 42, 0xc24b8b70); + RNDr(S, W, 43, 0xc76c51a3); + RNDr(S, W, 44, 0xd192e819); + RNDr(S, W, 45, 0xd6990624); + RNDr(S, W, 46, 0xf40e3585); + RNDr(S, W, 47, 0x106aa070); + RNDr(S, W, 48, 0x19a4c116); + RNDr(S, W, 49, 0x1e376c08); + RNDr(S, W, 50, 0x2748774c); + RNDr(S, W, 51, 0x34b0bcb5); + RNDr(S, W, 52, 0x391c0cb3); + RNDr(S, W, 53, 0x4ed8aa4a); + RNDr(S, W, 54, 0x5b9cca4f); + RNDr(S, W, 55, 0x682e6ff3); + RNDr(S, W, 56, 0x748f82ee); + RNDr(S, W, 57, 0x78a5636f); + RNDr(S, W, 58, 0x84c87814); + RNDr(S, W, 59, 0x8cc70208); + RNDr(S, W, 60, 0x90befffa); + RNDr(S, W, 61, 0xa4506ceb); + RNDr(S, W, 62, 0xbef9a3f7); + RNDr(S, W, 63, 0xc67178f2); + + /* 4. Mix local working variables into global state */ + for (i = 0; i < 8; i++) + state[i] += S[i]; +} + +static unsigned char PAD[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* Add padding and terminating bit-count. */ +static void +SHA256_Pad(SHA256_CTX * ctx) +{ + unsigned char len[8]; + uint32_t r, plen; + + /* + * Convert length to a vector of bytes -- we do this now rather + * than later because the length will change after we pad. + */ + be64enc(len, ctx->count); + + /* Add 1--64 bytes so that the resulting length is 56 mod 64 */ + r = (ctx->count >> 3) & 0x3f; + plen = (r < 56) ? (56 - r) : (120 - r); + SHA256_Update(ctx, PAD, (size_t)plen); + + /* Add the terminating bit-count */ + SHA256_Update(ctx, len, 8); +} + +/* SHA-256 initialization. Begins a SHA-256 operation. */ +void +SHA256_Init(SHA256_CTX * ctx) +{ + + /* Zero bits processed so far */ + ctx->count = 0; + + /* Magic initialization constants */ + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; +} + +/* Add bytes into the hash */ +void +SHA256_Update(SHA256_CTX * ctx, const void *in, size_t len) +{ + uint64_t bitlen; + uint32_t r; + const unsigned char *src = in; + + /* Number of bytes left in the buffer from previous updates */ + r = (ctx->count >> 3) & 0x3f; + + /* Convert the length into a number of bits */ + bitlen = len << 3; + + /* Update number of bits */ + ctx->count += bitlen; + + /* Handle the case where we don't need to perform any transforms */ + if (len < 64 - r) { + memcpy(&ctx->buf[r], src, len); + return; + } + + /* Finish the current block */ + memcpy(&ctx->buf[r], src, 64 - r); + SHA256_Transform(ctx->state, ctx->buf); + src += 64 - r; + len -= 64 - r; + + /* Perform complete blocks */ + while (len >= 64) { + SHA256_Transform(ctx->state, src); + src += 64; + len -= 64; + } + + /* Copy left over data into buffer */ + memcpy(ctx->buf, src, len); +} + +/* + * SHA-256 finalization. Pads the input data, exports the hash value, + * and clears the context state. + */ +void +SHA256_Final(unsigned char digest[32], SHA256_CTX * ctx) +{ + + /* Add padding */ + SHA256_Pad(ctx); + + /* Write the hash */ + be32enc_vect(digest, ctx->state, 32); + + /* Clear the context state */ + memset((void *)ctx, 0, sizeof(*ctx)); +} diff --git a/compat/crypt/sha256.h b/compat/crypt/sha256.h new file mode 100644 index 000000000000..02a09f935b20 --- /dev/null +++ b/compat/crypt/sha256.h @@ -0,0 +1,46 @@ +/*- + * Copyright 2005 Colin Percival + * 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. + * + * $FreeBSD$ + */ + +#ifndef SHA256_H_ +#define SHA256_H_ + +#include <sys/types.h> + +#define SHA256_DIGEST_LENGTH 32 + +typedef struct SHA256Context { + uint32_t state[8]; + uint64_t count; + unsigned char buf[64]; +} SHA256_CTX; + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX *, const void *, size_t); +void SHA256_Final(unsigned char [32], SHA256_CTX *); + +#endif diff --git a/compat/dprintf.c b/compat/dprintf.c new file mode 100644 index 000000000000..2ef81ade814b --- /dev/null +++ b/compat/dprintf.c @@ -0,0 +1,64 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2017 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. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> + +#include "dprintf.h" + +int +vdprintf(int fd, const char * __restrict fmt, va_list va) +{ + int e; + FILE *fp; + + if ((e = dup(fd)) == -1) + return -1; + + if ((fp = fdopen(e, "a")) == NULL) { + close(e); + return -1; + } + + e = vfprintf(fp, fmt, va); + fclose(fp); + return e; +} + +int +dprintf(int fd, const char * __restrict fmt, ...) +{ + int e; + va_list va; + + va_start(va, fmt); + e = vdprintf(fd, fmt, va); + va_end(va); + return e; +} diff --git a/compat/dprintf.h b/compat/dprintf.h new file mode 100644 index 000000000000..9defbcb835ff --- /dev/null +++ b/compat/dprintf.h @@ -0,0 +1,43 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2017 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. + */ + +#ifndef DPRINTF_H +#define DPRINTF_H + +#include <stdarg.h> + +#ifndef __printflike +# if __GNUC__ > 2 || defined(__INTEL_COMPILER) +# define __printflike(a, b) __attribute__((format(printf, a, b))) +# else +# define __printflike(a, b) +# endif +#endif + +__printflike(2, 0) int vdprintf(int, const char * __restrict, va_list); +__printflike(2, 3) int dprintf(int, const char * __restrict, ...); +#endif diff --git a/compat/endian.h b/compat/endian.h new file mode 100644 index 000000000000..8d01738bd242 --- /dev/null +++ b/compat/endian.h @@ -0,0 +1,71 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 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. + */ + +#ifndef ENDIAN_H +#define ENDIAN_H + +#include <stdint.h> + +inline static void +be32enc(uint8_t *buf, uint32_t u) +{ + + buf[0] = (uint8_t)((u >> 24) & 0xff); + buf[1] = (uint8_t)((u >> 16) & 0xff); + buf[2] = (uint8_t)((u >> 8) & 0xff); + buf[3] = (uint8_t)(u & 0xff); +} + +inline static void +be64enc(uint8_t *buf, uint64_t u) +{ + + be32enc(buf, (uint32_t)(u >> 32)); + be32enc(buf + sizeof(uint32_t), (uint32_t)(u & 0xffffffffULL)); +} + +inline static uint16_t +be16dec(const uint8_t *buf) +{ + + return (uint16_t)(buf[0] << 8 | buf[1]); +} + +inline static uint32_t +be32dec(const uint8_t *buf) +{ + + return (uint32_t)((uint32_t)be16dec(buf) << 16 | be16dec(buf + 2)); +} + +inline static uint64_t +be64dec(const uint8_t *buf) +{ + + return (uint64_t)((uint64_t)be32dec(buf) << 32 | be32dec(buf + 4)); +} +#endif diff --git a/compat/pidfile.c b/compat/pidfile.c new file mode 100644 index 000000000000..9dfd9c468a7a --- /dev/null +++ b/compat/pidfile.c @@ -0,0 +1,271 @@ +/* $NetBSD: pidfile.c,v 1.14 2016/04/12 20:40:43 roy Exp $ */ + +/*- + * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <paths.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <sys/file.h> /* for flock(2) */ +#include "config.h" +#include "defs.h" + +static pid_t pidfile_pid; +static char pidfile_path[PATH_MAX]; +static int pidfile_fd = -1; + +/* Closes pidfile resources. + * + * Returns 0 on success, otherwise -1. */ +static int +pidfile_close(void) +{ + int error; + + pidfile_pid = 0; + error = close(pidfile_fd); + pidfile_fd = -1; + pidfile_path[0] = '\0'; + return error; +} + +/* Truncate, close and unlink an existent pidfile, + * if and only if it was created by this process. + * The pidfile is truncated because we may have dropped permissions + * or entered a chroot and thus unable to unlink it. + * + * Returns 0 on truncation success, otherwise -1. */ +int +pidfile_clean(void) +{ + int error; + + if (pidfile_fd == -1) { + errno = EBADF; + return -1; + } + + if (pidfile_pid != getpid()) + error = EPERM; + else if (ftruncate(pidfile_fd, 0) == -1) + error = errno; + else { +#ifndef HAVE_PLEDGE /* Avoid a pledge violating segfault. */ + (void)unlink(pidfile_path); +#endif + error = 0; + } + + (void) pidfile_close(); + + if (error != 0) { + errno = error; + return -1; + } + return 0; +} + +/* atexit shim for pidfile_clean */ +static void +pidfile_cleanup(void) +{ + + pidfile_clean(); +} + +/* Constructs a name for a pidfile in the default location (/var/run). + * If 'bname' is NULL, uses the name of the current program for the name of + * the pidfile. + * + * Returns 0 on success, otherwise -1. */ +static int +pidfile_varrun_path(char *path, size_t len, const char *bname) +{ + + if (bname == NULL) + bname = PACKAGE; + + /* _PATH_VARRUN includes trailing / */ + if ((size_t)snprintf(path, len, "%s%s.pid", _PATH_VARRUN, bname) >= len) + { + errno = ENAMETOOLONG; + return -1; + } + return 0; +} + +/* Returns the process ID inside path on success, otherwise -1. + * If no path is given, use the last pidfile path, othewise the default one. */ +pid_t +pidfile_read(const char *path) +{ + char dpath[PATH_MAX], buf[16], *eptr; + int fd, error; + ssize_t n; + pid_t pid; + + if (path == NULL && pidfile_path[0] != '\0') + path = pidfile_path; + if (path == NULL || strchr(path, '/') == NULL) { + if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1) + return -1; + path = dpath; + } + + if ((fd = open(path, O_RDONLY | O_NONBLOCK)) == -1) + return -1; + n = read(fd, buf, sizeof(buf) - 1); + error = errno; + (void) close(fd); + if (n == -1) { + errno = error; + return -1; + } + buf[n] = '\0'; + pid = (pid_t)strtoi(buf, &eptr, 10, 1, INT_MAX, &error); + if (error && !(error == ENOTSUP && *eptr == '\n')) { + errno = error; + return -1; + } + return pid; +} + +/* Locks the pidfile specified by path and writes the process pid to it. + * The new pidfile is "registered" in the global variables pidfile_fd, + * pidfile_path and pidfile_pid so that any further call to pidfile_lock(3) + * can check if we are recreating the same file or a new one. + * + * Returns 0 on success, otherwise the pid of the process who owns the + * lock if it can be read, otherwise -1. */ +pid_t +pidfile_lock(const char *path) +{ + char dpath[PATH_MAX]; + static bool registered_atexit = false; + + /* Register for cleanup with atexit. */ + if (!registered_atexit) { + if (atexit(pidfile_cleanup) == -1) + return -1; + registered_atexit = true; + } + + if (path == NULL || strchr(path, '/') == NULL) { + if (pidfile_varrun_path(dpath, sizeof(dpath), path) == -1) + return -1; + path = dpath; + } + + /* If path has changed (no good reason), clean up the old pidfile. */ + if (pidfile_fd != -1 && strcmp(pidfile_path, path) != 0) + pidfile_clean(); + + if (pidfile_fd == -1) { + int fd, opts; + + opts = O_WRONLY | O_CREAT | O_NONBLOCK; +#ifdef O_CLOEXEC + opts |= O_CLOEXEC; +#endif +#ifdef O_EXLOCK + opts |= O_EXLOCK; +#endif + if ((fd = open(path, opts, 0644)) == -1) + goto return_pid; +#ifndef O_CLOEXEC + if ((opts = fcntl(fd, F_GETFD)) == -1 || + fcntl(fd, F_SETFL, opts | FD_CLOEXEC) == -1) + { + int error = errno; + + (void) close(fd); + errno = error; + return -1; + } +#endif +#ifndef O_EXLOCK + if (flock(fd, LOCK_EX | LOCK_NB) == -1) { + int error = errno; + + (void) close(fd); + if (error != EAGAIN) { + errno = error; + return -1; + } + fd = -1; + } +#endif + +return_pid: + if (fd == -1) { + pid_t pid; + + if (errno == EAGAIN) { + /* The pidfile is locked, return the process ID + * it contains. + * If sucessful, set errno to EEXIST. */ + if ((pid = pidfile_read(path)) != -1) + errno = EEXIST; + } else + pid = -1; + + return pid; + } + pidfile_fd = fd; + strlcpy(pidfile_path, path, sizeof(pidfile_path)); + } + + pidfile_pid = getpid(); + + /* Truncate the file, as we could be re-writing it. + * Then write the process ID. */ + if (ftruncate(pidfile_fd, 0) == -1 || + lseek(pidfile_fd, 0, SEEK_SET) == -1 || + dprintf(pidfile_fd, "%d\n", pidfile_pid) == -1) + { + int error = errno; + + pidfile_cleanup(); + errno = error; + return -1; + } + + /* Hold the fd open to persist the lock. */ + return 0; +} diff --git a/compat/pidfile.h b/compat/pidfile.h new file mode 100644 index 000000000000..ed833e15232c --- /dev/null +++ b/compat/pidfile.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 1999, 2016 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jason R. Thorpe, Matthias Scheler, Julio Merino and Roy Marples. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +#ifndef PIDFILE_H +#define PIDFILE_H + +#include <unistd.h> + +int pidfile_clean(void); +pid_t pidfile_lock(const char *); +pid_t pidfile_read(const char *); + +#endif diff --git a/compat/queue.h b/compat/queue.h new file mode 100644 index 000000000000..99ac6b9460ea --- /dev/null +++ b/compat/queue.h @@ -0,0 +1,175 @@ +/* $NetBSD: queue.h,v 1.65 2013/12/25 17:19:34 christos Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef COMPAT_QUEUE_H +#define COMPAT_QUEUE_H + +/* + * Tail queue definitions. + */ +#ifndef TAILQ_END +#define TAILQ_END(head) (NULL) +#endif + +#ifndef TAILQ_HEAD +#define _TAILQ_HEAD(name, type, qual) \ +struct name { \ + qual type *tqh_first; /* first element */ \ + qual type *qual *tqh_last; /* addr of last next element */ \ +} +#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) + +#define TAILQ_HEAD_INITIALIZER(head) \ + { TAILQ_END(head), &(head).tqh_first } + +#define _TAILQ_ENTRY(type, qual) \ +struct { \ + qual type *tqe_next; /* next element */ \ + qual type *qual *tqe_prev; /* address of previous next element */\ +} +#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) +#endif /* !TAILQ_HEAD */ + +/* + * Tail queue access methods. + */ +#ifndef TAILQ_FIRST +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head)) +#endif /* !TAILQ_FIRST */ + +#ifndef TAILQ_FOREACH +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->tqh_first); \ + (var) != TAILQ_END(head); \ + (var) = ((var)->field.tqe_next)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));\ + (var) != TAILQ_END(head); \ + (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) +#endif /* !TAILQ_FOREACH */ + +#ifndef TAILQ_INIT +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = TAILQ_END(head); \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = TAILQ_END(head); \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != \ + TAILQ_END(head)) \ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != TAILQ_END(head)) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) +#endif /* !TAILQ_INIT */ + +#ifndef TAILQ_REPLACE +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != \ + TAILQ_END(head)) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ +} while (/*CONSTCOND*/0) +#endif /* !TAILQ_REPLACE */ + +#ifndef TAILQ_FOREACH_SAFE +#define TAILQ_FOREACH_SAFE(var, head, field, next) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && \ + ((next) = TAILQ_NEXT(var, field), 1); (var) = (next)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) != TAILQ_END(head) && \ + ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev)) +#endif /* !TAILQ_FOREACH_SAFE */ + +#ifndef TAILQ_CONCAT +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) +#endif /* !TAILQ_CONCAT */ + +#endif /* !COMAPT_QUEUE_H */ diff --git a/compat/rb.c b/compat/rb.c new file mode 100644 index 000000000000..3c0bed5f70d5 --- /dev/null +++ b/compat/rb.c @@ -0,0 +1,1346 @@ +/* $NetBSD: rb.c,v 1.14 2019/03/08 09:14:54 roy Exp $ */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Matt Thomas <matt@3am-software.com>. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "common.h" + +#if !defined(_KERNEL) && !defined(_STANDALONE) +#include <sys/types.h> +#include <stddef.h> +#include <assert.h> +#include <stdbool.h> +#ifdef RBDEBUG +#define KASSERT(s) assert(s) +#define __rbt_unused +#else +#define KASSERT(s) do { } while (/*CONSTCOND*/ 0) +#define __rbt_unused __unused +#endif +__RCSID("$NetBSD: rb.c,v 1.14 2019/03/08 09:14:54 roy Exp $"); +#else +#include <lib/libkern/libkern.h> +__KERNEL_RCSID(0, "$NetBSD: rb.c,v 1.14 2019/03/08 09:14:54 roy Exp $"); +#ifndef DIAGNOSTIC +#define __rbt_unused __unused +#else +#define __rbt_unused +#endif +#endif + +#ifdef _LIBC +__weak_alias(rb_tree_init, _rb_tree_init) +__weak_alias(rb_tree_find_node, _rb_tree_find_node) +__weak_alias(rb_tree_find_node_geq, _rb_tree_find_node_geq) +__weak_alias(rb_tree_find_node_leq, _rb_tree_find_node_leq) +__weak_alias(rb_tree_insert_node, _rb_tree_insert_node) +__weak_alias(rb_tree_remove_node, _rb_tree_remove_node) +__weak_alias(rb_tree_iterate, _rb_tree_iterate) +#ifdef RBDEBUG +__weak_alias(rb_tree_check, _rb_tree_check) +__weak_alias(rb_tree_depths, _rb_tree_depths) +#endif + +#include "namespace.h" +#endif + +#ifdef RBTEST +#include "rbtree.h" +#else +#include <sys/rbtree.h> +#endif + +static void rb_tree_insert_rebalance(struct rb_tree *, struct rb_node *); +static void rb_tree_removal_rebalance(struct rb_tree *, struct rb_node *, + unsigned int); +#ifdef RBDEBUG +static const struct rb_node *rb_tree_iterate_const(const struct rb_tree *, + const struct rb_node *, const unsigned int); +static bool rb_tree_check_node(const struct rb_tree *, const struct rb_node *, + const struct rb_node *, bool); +#else +#define rb_tree_check_node(a, b, c, d) true +#endif + +#define RB_NODETOITEM(rbto, rbn) \ + ((void *)((uintptr_t)(rbn) - (rbto)->rbto_node_offset)) +#define RB_ITEMTONODE(rbto, rbn) \ + ((rb_node_t *)((uintptr_t)(rbn) + (rbto)->rbto_node_offset)) + +#define RB_SENTINEL_NODE NULL + +void +rb_tree_init(struct rb_tree *rbt, const rb_tree_ops_t *ops) +{ + + rbt->rbt_ops = ops; + rbt->rbt_root = RB_SENTINEL_NODE; + RB_TAILQ_INIT(&rbt->rbt_nodes); +#ifndef RBSMALL + rbt->rbt_minmax[RB_DIR_LEFT] = rbt->rbt_root; /* minimum node */ + rbt->rbt_minmax[RB_DIR_RIGHT] = rbt->rbt_root; /* maximum node */ +#endif +#ifdef RBSTATS + rbt->rbt_count = 0; + rbt->rbt_insertions = 0; + rbt->rbt_removals = 0; + rbt->rbt_insertion_rebalance_calls = 0; + rbt->rbt_insertion_rebalance_passes = 0; + rbt->rbt_removal_rebalance_calls = 0; + rbt->rbt_removal_rebalance_passes = 0; +#endif +} + +void * +rb_tree_find_node(struct rb_tree *rbt, const void *key) +{ + const rb_tree_ops_t *rbto = rbt->rbt_ops; + rbto_compare_key_fn compare_key = rbto->rbto_compare_key; + struct rb_node *parent = rbt->rbt_root; + + while (!RB_SENTINEL_P(parent)) { + void *pobj = RB_NODETOITEM(rbto, parent); + const signed int diff = (*compare_key)(rbto->rbto_context, + pobj, key); + if (diff == 0) + return pobj; + parent = parent->rb_nodes[diff < 0]; + } + + return NULL; +} + +void * +rb_tree_find_node_geq(struct rb_tree *rbt, const void *key) +{ + const rb_tree_ops_t *rbto = rbt->rbt_ops; + rbto_compare_key_fn compare_key = rbto->rbto_compare_key; + struct rb_node *parent = rbt->rbt_root, *last = NULL; + + while (!RB_SENTINEL_P(parent)) { + void *pobj = RB_NODETOITEM(rbto, parent); + const signed int diff = (*compare_key)(rbto->rbto_context, + pobj, key); + if (diff == 0) + return pobj; + if (diff > 0) + last = parent; + parent = parent->rb_nodes[diff < 0]; + } + + return last == NULL ? NULL : RB_NODETOITEM(rbto, last); +} + +void * +rb_tree_find_node_leq(struct rb_tree *rbt, const void *key) +{ + const rb_tree_ops_t *rbto = rbt->rbt_ops; + rbto_compare_key_fn compare_key = rbto->rbto_compare_key; + struct rb_node *parent = rbt->rbt_root, *last = NULL; + + while (!RB_SENTINEL_P(parent)) { + void *pobj = RB_NODETOITEM(rbto, parent); + const signed int diff = (*compare_key)(rbto->rbto_context, + pobj, key); + if (diff == 0) + return pobj; + if (diff < 0) + last = parent; + parent = parent->rb_nodes[diff < 0]; + } + + return last == NULL ? NULL : RB_NODETOITEM(rbto, last); +} + +void * +rb_tree_insert_node(struct rb_tree *rbt, void *object) +{ + const rb_tree_ops_t *rbto = rbt->rbt_ops; + rbto_compare_nodes_fn compare_nodes = rbto->rbto_compare_nodes; + struct rb_node *parent, *tmp, *self = RB_ITEMTONODE(rbto, object); + unsigned int position; + bool rebalance; + + RBSTAT_INC(rbt->rbt_insertions); + + tmp = rbt->rbt_root; + /* + * This is a hack. Because rbt->rbt_root is just a struct rb_node *, + * just like rb_node->rb_nodes[RB_DIR_LEFT], we can use this fact to + * avoid a lot of tests for root and know that even at root, + * updating RB_FATHER(rb_node)->rb_nodes[RB_POSITION(rb_node)] will + * update rbt->rbt_root. + */ + parent = (struct rb_node *)(void *)&rbt->rbt_root; + position = RB_DIR_LEFT; + + /* + * Find out where to place this new leaf. + */ + while (!RB_SENTINEL_P(tmp)) { + void *tobj = RB_NODETOITEM(rbto, tmp); + const signed int diff = (*compare_nodes)(rbto->rbto_context, + tobj, object); + if (__predict_false(diff == 0)) { + /* + * Node already exists; return it. + */ + return tobj; + } + parent = tmp; + position = (diff < 0); + tmp = parent->rb_nodes[position]; + } + +#ifdef RBDEBUG + { + struct rb_node *prev = NULL, *next = NULL; + + if (position == RB_DIR_RIGHT) + prev = parent; + else if (tmp != rbt->rbt_root) + next = parent; + + /* + * Verify our sequential position + */ + KASSERT(prev == NULL || !RB_SENTINEL_P(prev)); + KASSERT(next == NULL || !RB_SENTINEL_P(next)); + if (prev != NULL && next == NULL) + next = TAILQ_NEXT(prev, rb_link); + if (prev == NULL && next != NULL) + prev = TAILQ_PREV(next, rb_node_qh, rb_link); + KASSERT(prev == NULL || !RB_SENTINEL_P(prev)); + KASSERT(next == NULL || !RB_SENTINEL_P(next)); + KASSERT(prev == NULL || (*compare_nodes)(rbto->rbto_context, + RB_NODETOITEM(rbto, prev), RB_NODETOITEM(rbto, self)) < 0); + KASSERT(next == NULL || (*compare_nodes)(rbto->rbto_context, + RB_NODETOITEM(rbto, self), RB_NODETOITEM(rbto, next)) < 0); + } +#endif + + /* + * Initialize the node and insert as a leaf into the tree. + */ + RB_SET_FATHER(self, parent); + RB_SET_POSITION(self, position); + if (__predict_false(parent == (struct rb_node *)(void *)&rbt->rbt_root)) { + RB_MARK_BLACK(self); /* root is always black */ +#ifndef RBSMALL + rbt->rbt_minmax[RB_DIR_LEFT] = self; + rbt->rbt_minmax[RB_DIR_RIGHT] = self; +#endif + rebalance = false; + } else { + KASSERT(position == RB_DIR_LEFT || position == RB_DIR_RIGHT); +#ifndef RBSMALL + /* + * Keep track of the minimum and maximum nodes. If our + * parent is a minmax node and we on their min/max side, + * we must be the new min/max node. + */ + if (parent == rbt->rbt_minmax[position]) + rbt->rbt_minmax[position] = self; +#endif /* !RBSMALL */ + /* + * All new nodes are colored red. We only need to rebalance + * if our parent is also red. + */ + RB_MARK_RED(self); + rebalance = RB_RED_P(parent); + } + KASSERT(RB_SENTINEL_P(parent->rb_nodes[position])); + self->rb_left = parent->rb_nodes[position]; + self->rb_right = parent->rb_nodes[position]; + parent->rb_nodes[position] = self; + KASSERT(RB_CHILDLESS_P(self)); + + /* + * Insert the new node into a sorted list for easy sequential access + */ + RBSTAT_INC(rbt->rbt_count); +#ifdef RBDEBUG + if (RB_ROOT_P(rbt, self)) { + RB_TAILQ_INSERT_HEAD(&rbt->rbt_nodes, self, rb_link); + } else if (position == RB_DIR_LEFT) { + KASSERT((*compare_nodes)(rbto->rbto_context, + RB_NODETOITEM(rbto, self), + RB_NODETOITEM(rbto, RB_FATHER(self))) < 0); + RB_TAILQ_INSERT_BEFORE(RB_FATHER(self), self, rb_link); + } else { + KASSERT((*compare_nodes)(rbto->rbto_context, + RB_NODETOITEM(rbto, RB_FATHER(self)), + RB_NODETOITEM(rbto, self)) < 0); + RB_TAILQ_INSERT_AFTER(&rbt->rbt_nodes, RB_FATHER(self), + self, rb_link); + } +#endif + KASSERT(rb_tree_check_node(rbt, self, NULL, !rebalance)); + + /* + * Rebalance tree after insertion + */ + if (rebalance) { + rb_tree_insert_rebalance(rbt, self); + KASSERT(rb_tree_check_node(rbt, self, NULL, true)); + } + + /* Succesfully inserted, return our node pointer. */ + return object; +} + +/* + * Swap the location and colors of 'self' and its child @ which. The child + * can not be a sentinel node. This is our rotation function. However, + * since it preserves coloring, it great simplifies both insertion and + * removal since rotation almost always involves the exchanging of colors + * as a separate step. + */ +static void +rb_tree_reparent_nodes(__rbt_unused struct rb_tree *rbt, + struct rb_node *old_father, const unsigned int which) +{ + const unsigned int other = which ^ RB_DIR_OTHER; + struct rb_node * const grandpa = RB_FATHER(old_father); + struct rb_node * const old_child = old_father->rb_nodes[which]; + struct rb_node * const new_father = old_child; + struct rb_node * const new_child = old_father; + + KASSERT(which == RB_DIR_LEFT || which == RB_DIR_RIGHT); + + KASSERT(!RB_SENTINEL_P(old_child)); + KASSERT(RB_FATHER(old_child) == old_father); + + KASSERT(rb_tree_check_node(rbt, old_father, NULL, false)); + KASSERT(rb_tree_check_node(rbt, old_child, NULL, false)); + KASSERT(RB_ROOT_P(rbt, old_father) || + rb_tree_check_node(rbt, grandpa, NULL, false)); + + /* + * Exchange descendant linkages. + */ + grandpa->rb_nodes[RB_POSITION(old_father)] = new_father; + new_child->rb_nodes[which] = old_child->rb_nodes[other]; + new_father->rb_nodes[other] = new_child; + + /* + * Update ancestor linkages + */ + RB_SET_FATHER(new_father, grandpa); + RB_SET_FATHER(new_child, new_father); + + /* + * Exchange properties between new_father and new_child. The only + * change is that new_child's position is now on the other side. + */ +#if 0 + { + struct rb_node tmp; + tmp.rb_info = 0; + RB_COPY_PROPERTIES(&tmp, old_child); + RB_COPY_PROPERTIES(new_father, old_father); + RB_COPY_PROPERTIES(new_child, &tmp); + } +#else + RB_SWAP_PROPERTIES(new_father, new_child); +#endif + RB_SET_POSITION(new_child, other); + + /* + * Make sure to reparent the new child to ourself. + */ + if (!RB_SENTINEL_P(new_child->rb_nodes[which])) { + RB_SET_FATHER(new_child->rb_nodes[which], new_child); + RB_SET_POSITION(new_child->rb_nodes[which], which); + } + + KASSERT(rb_tree_check_node(rbt, new_father, NULL, false)); + KASSERT(rb_tree_check_node(rbt, new_child, NULL, false)); + KASSERT(RB_ROOT_P(rbt, new_father) || + rb_tree_check_node(rbt, grandpa, NULL, false)); +} + +static void +rb_tree_insert_rebalance(struct rb_tree *rbt, struct rb_node *self) +{ + struct rb_node * father = RB_FATHER(self); + struct rb_node * grandpa = RB_FATHER(father); + struct rb_node * uncle; + unsigned int which; + unsigned int other; + + KASSERT(!RB_ROOT_P(rbt, self)); + KASSERT(RB_RED_P(self)); + KASSERT(RB_RED_P(father)); + RBSTAT_INC(rbt->rbt_insertion_rebalance_calls); + + for (;;) { + KASSERT(!RB_SENTINEL_P(self)); + + KASSERT(RB_RED_P(self)); + KASSERT(RB_RED_P(father)); + /* + * We are red and our parent is red, therefore we must have a + * grandfather and he must be black. + */ + grandpa = RB_FATHER(father); + KASSERT(RB_BLACK_P(grandpa)); + KASSERT(RB_DIR_RIGHT == 1 && RB_DIR_LEFT == 0); + which = (father == grandpa->rb_right); + other = which ^ RB_DIR_OTHER; + uncle = grandpa->rb_nodes[other]; + + if (RB_BLACK_P(uncle)) + break; + + RBSTAT_INC(rbt->rbt_insertion_rebalance_passes); + /* + * Case 1: our uncle is red + * Simply invert the colors of our parent and + * uncle and make our grandparent red. And + * then solve the problem up at his level. + */ + RB_MARK_BLACK(uncle); + RB_MARK_BLACK(father); + if (__predict_false(RB_ROOT_P(rbt, grandpa))) { + /* + * If our grandpa is root, don't bother + * setting him to red, just return. + */ + KASSERT(RB_BLACK_P(grandpa)); + return; + } + RB_MARK_RED(grandpa); + self = grandpa; + father = RB_FATHER(self); + KASSERT(RB_RED_P(self)); + if (RB_BLACK_P(father)) { + /* + * If our greatgrandpa is black, we're done. + */ + KASSERT(RB_BLACK_P(rbt->rbt_root)); + return; + } + } + + KASSERT(!RB_ROOT_P(rbt, self)); + KASSERT(RB_RED_P(self)); + KASSERT(RB_RED_P(father)); + KASSERT(RB_BLACK_P(uncle)); + KASSERT(RB_BLACK_P(grandpa)); + /* + * Case 2&3: our uncle is black. + */ + if (self == father->rb_nodes[other]) { + /* + * Case 2: we are on the same side as our uncle + * Swap ourselves with our parent so this case + * becomes case 3. Basically our parent becomes our + * child. + */ + rb_tree_reparent_nodes(rbt, father, other); + KASSERT(RB_FATHER(father) == self); + KASSERT(self->rb_nodes[which] == father); + KASSERT(RB_FATHER(self) == grandpa); + self = father; + father = RB_FATHER(self); + } + KASSERT(RB_RED_P(self) && RB_RED_P(father)); + KASSERT(grandpa->rb_nodes[which] == father); + /* + * Case 3: we are opposite a child of a black uncle. + * Swap our parent and grandparent. Since our grandfather + * is black, our father will become black and our new sibling + * (former grandparent) will become red. + */ + rb_tree_reparent_nodes(rbt, grandpa, which); + KASSERT(RB_FATHER(self) == father); + KASSERT(RB_FATHER(self)->rb_nodes[RB_POSITION(self) ^ RB_DIR_OTHER] == grandpa); + KASSERT(RB_RED_P(self)); + KASSERT(RB_BLACK_P(father)); + KASSERT(RB_RED_P(grandpa)); + + /* + * Final step: Set the root to black. + */ + RB_MARK_BLACK(rbt->rbt_root); +} + +static void +rb_tree_prune_node(struct rb_tree *rbt, struct rb_node *self, bool rebalance) +{ + const unsigned int which = RB_POSITION(self); + struct rb_node *father = RB_FATHER(self); +#ifndef RBSMALL + const bool was_root = RB_ROOT_P(rbt, self); +#endif + + KASSERT(rebalance || (RB_ROOT_P(rbt, self) || RB_RED_P(self))); + KASSERT(!rebalance || RB_BLACK_P(self)); + KASSERT(RB_CHILDLESS_P(self)); + KASSERT(rb_tree_check_node(rbt, self, NULL, false)); + + /* + * Since we are childless, we know that self->rb_left is pointing + * to the sentinel node. + */ + father->rb_nodes[which] = self->rb_left; + + /* + * Remove ourselves from the node list, decrement the count, + * and update min/max. + */ + RB_TAILQ_REMOVE(&rbt->rbt_nodes, self, rb_link); + RBSTAT_DEC(rbt->rbt_count); +#ifndef RBSMALL + if (__predict_false(rbt->rbt_minmax[RB_POSITION(self)] == self)) { + rbt->rbt_minmax[RB_POSITION(self)] = father; + /* + * When removing the root, rbt->rbt_minmax[RB_DIR_LEFT] is + * updated automatically, but we also need to update + * rbt->rbt_minmax[RB_DIR_RIGHT]; + */ + if (__predict_false(was_root)) { + rbt->rbt_minmax[RB_DIR_RIGHT] = father; + } + } + RB_SET_FATHER(self, NULL); +#endif + + /* + * Rebalance if requested. + */ + if (rebalance) + rb_tree_removal_rebalance(rbt, father, which); + KASSERT(was_root || rb_tree_check_node(rbt, father, NULL, true)); +} + +/* + * When deleting an interior node + */ +static void +rb_tree_swap_prune_and_rebalance(struct rb_tree *rbt, struct rb_node *self, + struct rb_node *standin) +{ + const unsigned int standin_which = RB_POSITION(standin); + unsigned int standin_other = standin_which ^ RB_DIR_OTHER; + struct rb_node *standin_son; + struct rb_node *standin_father = RB_FATHER(standin); + bool rebalance = RB_BLACK_P(standin); + + if (standin_father == self) { + /* + * As a child of self, any childen would be opposite of + * our parent. + */ + KASSERT(RB_SENTINEL_P(standin->rb_nodes[standin_other])); + standin_son = standin->rb_nodes[standin_which]; + } else { + /* + * Since we aren't a child of self, any childen would be + * on the same side as our parent. + */ + KASSERT(RB_SENTINEL_P(standin->rb_nodes[standin_which])); + standin_son = standin->rb_nodes[standin_other]; + } + + /* + * the node we are removing must have two children. + */ + KASSERT(RB_TWOCHILDREN_P(self)); + /* + * If standin has a child, it must be red. + */ + KASSERT(RB_SENTINEL_P(standin_son) || RB_RED_P(standin_son)); + + /* + * Verify things are sane. + */ + KASSERT(rb_tree_check_node(rbt, self, NULL, false)); + KASSERT(rb_tree_check_node(rbt, standin, NULL, false)); + + if (__predict_false(RB_RED_P(standin_son))) { + /* + * We know we have a red child so if we flip it to black + * we don't have to rebalance. + */ + KASSERT(rb_tree_check_node(rbt, standin_son, NULL, true)); + RB_MARK_BLACK(standin_son); + rebalance = false; + + if (standin_father == self) { + KASSERT(RB_POSITION(standin_son) == standin_which); + } else { + KASSERT(RB_POSITION(standin_son) == standin_other); + /* + * Change the son's parentage to point to his grandpa. + */ + RB_SET_FATHER(standin_son, standin_father); + RB_SET_POSITION(standin_son, standin_which); + } + } + + if (standin_father == self) { + /* + * If we are about to delete the standin's father, then when + * we call rebalance, we need to use ourselves as our father. + * Otherwise remember our original father. Also, sincef we are + * our standin's father we only need to reparent the standin's + * brother. + * + * | R --> S | + * | Q S --> Q T | + * | t --> | + */ + KASSERT(RB_SENTINEL_P(standin->rb_nodes[standin_other])); + KASSERT(!RB_SENTINEL_P(self->rb_nodes[standin_other])); + KASSERT(self->rb_nodes[standin_which] == standin); + /* + * Have our son/standin adopt his brother as his new son. + */ + standin_father = standin; + } else { + /* + * | R --> S . | + * | / \ | T --> / \ | / | + * | ..... | S --> ..... | T | + * + * Sever standin's connection to his father. + */ + standin_father->rb_nodes[standin_which] = standin_son; + /* + * Adopt the far son. + */ + standin->rb_nodes[standin_other] = self->rb_nodes[standin_other]; + RB_SET_FATHER(standin->rb_nodes[standin_other], standin); + KASSERT(RB_POSITION(self->rb_nodes[standin_other]) == standin_other); + /* + * Use standin_other because we need to preserve standin_which + * for the removal_rebalance. + */ + standin_other = standin_which; + } + + /* + * Move the only remaining son to our standin. If our standin is our + * son, this will be the only son needed to be moved. + */ + KASSERT(standin->rb_nodes[standin_other] != self->rb_nodes[standin_other]); + standin->rb_nodes[standin_other] = self->rb_nodes[standin_other]; + RB_SET_FATHER(standin->rb_nodes[standin_other], standin); + + /* + * Now copy the result of self to standin and then replace + * self with standin in the tree. + */ + RB_COPY_PROPERTIES(standin, self); + RB_SET_FATHER(standin, RB_FATHER(self)); + RB_FATHER(standin)->rb_nodes[RB_POSITION(standin)] = standin; + + /* + * Remove ourselves from the node list, decrement the count, + * and update min/max. + */ + RB_TAILQ_REMOVE(&rbt->rbt_nodes, self, rb_link); + RBSTAT_DEC(rbt->rbt_count); +#ifndef RBSMALL + if (__predict_false(rbt->rbt_minmax[RB_POSITION(self)] == self)) + rbt->rbt_minmax[RB_POSITION(self)] = RB_FATHER(self); + RB_SET_FATHER(self, NULL); +#endif + + KASSERT(rb_tree_check_node(rbt, standin, NULL, false)); + KASSERT(RB_FATHER_SENTINEL_P(standin) + || rb_tree_check_node(rbt, standin_father, NULL, false)); + KASSERT(RB_LEFT_SENTINEL_P(standin) + || rb_tree_check_node(rbt, standin->rb_left, NULL, false)); + KASSERT(RB_RIGHT_SENTINEL_P(standin) + || rb_tree_check_node(rbt, standin->rb_right, NULL, false)); + + if (!rebalance) + return; + + rb_tree_removal_rebalance(rbt, standin_father, standin_which); + KASSERT(rb_tree_check_node(rbt, standin, NULL, true)); +} + +/* + * We could do this by doing + * rb_tree_node_swap(rbt, self, which); + * rb_tree_prune_node(rbt, self, false); + * + * But it's more efficient to just evalate and recolor the child. + */ +static void +rb_tree_prune_blackred_branch(struct rb_tree *rbt, struct rb_node *self, + unsigned int which) +{ + struct rb_node *father = RB_FATHER(self); + struct rb_node *son = self->rb_nodes[which]; +#ifndef RBSMALL + const bool was_root = RB_ROOT_P(rbt, self); +#endif + + KASSERT(which == RB_DIR_LEFT || which == RB_DIR_RIGHT); + KASSERT(RB_BLACK_P(self) && RB_RED_P(son)); + KASSERT(!RB_TWOCHILDREN_P(son)); + KASSERT(RB_CHILDLESS_P(son)); + KASSERT(rb_tree_check_node(rbt, self, NULL, false)); + KASSERT(rb_tree_check_node(rbt, son, NULL, false)); + + /* + * Remove ourselves from the tree and give our former child our + * properties (position, color, root). + */ + RB_COPY_PROPERTIES(son, self); + father->rb_nodes[RB_POSITION(son)] = son; + RB_SET_FATHER(son, father); + + /* + * Remove ourselves from the node list, decrement the count, + * and update minmax. + */ + RB_TAILQ_REMOVE(&rbt->rbt_nodes, self, rb_link); + RBSTAT_DEC(rbt->rbt_count); +#ifndef RBSMALL + if (__predict_false(was_root)) { + KASSERT(rbt->rbt_minmax[which] == son); + rbt->rbt_minmax[which ^ RB_DIR_OTHER] = son; + } else if (rbt->rbt_minmax[RB_POSITION(self)] == self) { + rbt->rbt_minmax[RB_POSITION(self)] = son; + } + RB_SET_FATHER(self, NULL); +#endif + + KASSERT(was_root || rb_tree_check_node(rbt, father, NULL, true)); + KASSERT(rb_tree_check_node(rbt, son, NULL, true)); +} + +void +rb_tree_remove_node(struct rb_tree *rbt, void *object) +{ + const rb_tree_ops_t *rbto = rbt->rbt_ops; + struct rb_node *standin, *self = RB_ITEMTONODE(rbto, object); + unsigned int which; + + KASSERT(!RB_SENTINEL_P(self)); + RBSTAT_INC(rbt->rbt_removals); + + /* + * In the following diagrams, we (the node to be removed) are S. Red + * nodes are lowercase. T could be either red or black. + * + * Remember the major axiom of the red-black tree: the number of + * black nodes from the root to each leaf is constant across all + * leaves, only the number of red nodes varies. + * + * Thus removing a red leaf doesn't require any other changes to a + * red-black tree. So if we must remove a node, attempt to rearrange + * the tree so we can remove a red node. + * + * The simpliest case is a childless red node or a childless root node: + * + * | T --> T | or | R --> * | + * | s --> * | + */ + if (RB_CHILDLESS_P(self)) { + const bool rebalance = RB_BLACK_P(self) && !RB_ROOT_P(rbt, self); + rb_tree_prune_node(rbt, self, rebalance); + return; + } + KASSERT(!RB_CHILDLESS_P(self)); + if (!RB_TWOCHILDREN_P(self)) { + /* + * The next simpliest case is the node we are deleting is + * black and has one red child. + * + * | T --> T --> T | + * | S --> R --> R | + * | r --> s --> * | + */ + which = RB_LEFT_SENTINEL_P(self) ? RB_DIR_RIGHT : RB_DIR_LEFT; + KASSERT(RB_BLACK_P(self)); + KASSERT(RB_RED_P(self->rb_nodes[which])); + KASSERT(RB_CHILDLESS_P(self->rb_nodes[which])); + rb_tree_prune_blackred_branch(rbt, self, which); + return; + } + KASSERT(RB_TWOCHILDREN_P(self)); + + /* + * We invert these because we prefer to remove from the inside of + * the tree. + */ + which = RB_POSITION(self) ^ RB_DIR_OTHER; + + /* + * Let's find the node closes to us opposite of our parent + * Now swap it with ourself, "prune" it, and rebalance, if needed. + */ + standin = RB_ITEMTONODE(rbto, rb_tree_iterate(rbt, object, which)); + rb_tree_swap_prune_and_rebalance(rbt, self, standin); +} + +static void +rb_tree_removal_rebalance(struct rb_tree *rbt, struct rb_node *parent, + unsigned int which) +{ + KASSERT(!RB_SENTINEL_P(parent)); + KASSERT(RB_SENTINEL_P(parent->rb_nodes[which])); + KASSERT(which == RB_DIR_LEFT || which == RB_DIR_RIGHT); + RBSTAT_INC(rbt->rbt_removal_rebalance_calls); + + while (RB_BLACK_P(parent->rb_nodes[which])) { + unsigned int other = which ^ RB_DIR_OTHER; + struct rb_node *brother = parent->rb_nodes[other]; + + RBSTAT_INC(rbt->rbt_removal_rebalance_passes); + + KASSERT(!RB_SENTINEL_P(brother)); + /* + * For cases 1, 2a, and 2b, our brother's children must + * be black and our father must be black + */ + if (RB_BLACK_P(parent) + && RB_BLACK_P(brother->rb_left) + && RB_BLACK_P(brother->rb_right)) { + if (RB_RED_P(brother)) { + /* + * Case 1: Our brother is red, swap its + * position (and colors) with our parent. + * This should now be case 2b (unless C or E + * has a red child which is case 3; thus no + * explicit branch to case 2b). + * + * B -> D + * A d -> b E + * C E -> A C + */ + KASSERT(RB_BLACK_P(parent)); + rb_tree_reparent_nodes(rbt, parent, other); + brother = parent->rb_nodes[other]; + KASSERT(!RB_SENTINEL_P(brother)); + KASSERT(RB_RED_P(parent)); + KASSERT(RB_BLACK_P(brother)); + KASSERT(rb_tree_check_node(rbt, brother, NULL, false)); + KASSERT(rb_tree_check_node(rbt, parent, NULL, false)); + } else { + /* + * Both our parent and brother are black. + * Change our brother to red, advance up rank + * and go through the loop again. + * + * B -> *B + * *A D -> A d + * C E -> C E + */ + RB_MARK_RED(brother); + KASSERT(RB_BLACK_P(brother->rb_left)); + KASSERT(RB_BLACK_P(brother->rb_right)); + if (RB_ROOT_P(rbt, parent)) + return; /* root == parent == black */ + KASSERT(rb_tree_check_node(rbt, brother, NULL, false)); + KASSERT(rb_tree_check_node(rbt, parent, NULL, false)); + which = RB_POSITION(parent); + parent = RB_FATHER(parent); + continue; + } + } + /* + * Avoid an else here so that case 2a above can hit either + * case 2b, 3, or 4. + */ + if (RB_RED_P(parent) + && RB_BLACK_P(brother) + && RB_BLACK_P(brother->rb_left) + && RB_BLACK_P(brother->rb_right)) { + KASSERT(RB_RED_P(parent)); + KASSERT(RB_BLACK_P(brother)); + KASSERT(RB_BLACK_P(brother->rb_left)); + KASSERT(RB_BLACK_P(brother->rb_right)); + /* + * We are black, our father is red, our brother and + * both nephews are black. Simply invert/exchange the + * colors of our father and brother (to black and red + * respectively). + * + * | f --> F | + * | * B --> * b | + * | N N --> N N | + */ + RB_MARK_BLACK(parent); + RB_MARK_RED(brother); + KASSERT(rb_tree_check_node(rbt, brother, NULL, true)); + break; /* We're done! */ + } else { + /* + * Our brother must be black and have at least one + * red child (it may have two). + */ + KASSERT(RB_BLACK_P(brother)); + KASSERT(RB_RED_P(brother->rb_nodes[which]) || + RB_RED_P(brother->rb_nodes[other])); + if (RB_BLACK_P(brother->rb_nodes[other])) { + /* + * Case 3: our brother is black, our near + * nephew is red, and our far nephew is black. + * Swap our brother with our near nephew. + * This result in a tree that matches case 4. + * (Our father could be red or black). + * + * | F --> F | + * | x B --> x B | + * | n --> n | + */ + KASSERT(RB_RED_P(brother->rb_nodes[which])); + rb_tree_reparent_nodes(rbt, brother, which); + KASSERT(RB_FATHER(brother) == parent->rb_nodes[other]); + brother = parent->rb_nodes[other]; + KASSERT(RB_RED_P(brother->rb_nodes[other])); + } + /* + * Case 4: our brother is black and our far nephew + * is red. Swap our father and brother locations and + * change our far nephew to black. (these can be + * done in either order so we change the color first). + * The result is a valid red-black tree and is a + * terminal case. (again we don't care about the + * father's color) + * + * If the father is red, we will get a red-black-black + * tree: + * | f -> f --> b | + * | B -> B --> F N | + * | n -> N --> | + * + * If the father is black, we will get an all black + * tree: + * | F -> F --> B | + * | B -> B --> F N | + * | n -> N --> | + * + * If we had two red nephews, then after the swap, + * our former father would have a red grandson. + */ + KASSERT(RB_BLACK_P(brother)); + KASSERT(RB_RED_P(brother->rb_nodes[other])); + RB_MARK_BLACK(brother->rb_nodes[other]); + rb_tree_reparent_nodes(rbt, parent, other); + break; /* We're done! */ + } + } + KASSERT(rb_tree_check_node(rbt, parent, NULL, true)); +} + +void * +rb_tree_iterate(struct rb_tree *rbt, void *object, const unsigned int direction) +{ + const rb_tree_ops_t *rbto = rbt->rbt_ops; + const unsigned int other = direction ^ RB_DIR_OTHER; + struct rb_node *self; + + KASSERT(direction == RB_DIR_LEFT || direction == RB_DIR_RIGHT); + + if (object == NULL) { +#ifndef RBSMALL + if (RB_SENTINEL_P(rbt->rbt_root)) + return NULL; + return RB_NODETOITEM(rbto, rbt->rbt_minmax[direction]); +#else + self = rbt->rbt_root; + if (RB_SENTINEL_P(self)) + return NULL; + while (!RB_SENTINEL_P(self->rb_nodes[direction])) + self = self->rb_nodes[direction]; + return RB_NODETOITEM(rbto, self); +#endif /* !RBSMALL */ + } + self = RB_ITEMTONODE(rbto, object); + KASSERT(!RB_SENTINEL_P(self)); + /* + * We can't go any further in this direction. We proceed up in the + * opposite direction until our parent is in direction we want to go. + */ + if (RB_SENTINEL_P(self->rb_nodes[direction])) { + while (!RB_ROOT_P(rbt, self)) { + if (other == RB_POSITION(self)) + return RB_NODETOITEM(rbto, RB_FATHER(self)); + self = RB_FATHER(self); + } + return NULL; + } + + /* + * Advance down one in current direction and go down as far as possible + * in the opposite direction. + */ + self = self->rb_nodes[direction]; + KASSERT(!RB_SENTINEL_P(self)); + while (!RB_SENTINEL_P(self->rb_nodes[other])) + self = self->rb_nodes[other]; + return RB_NODETOITEM(rbto, self); +} + +#ifdef RBDEBUG +static const struct rb_node * +rb_tree_iterate_const(const struct rb_tree *rbt, const struct rb_node *self, + const unsigned int direction) +{ + const unsigned int other = direction ^ RB_DIR_OTHER; + KASSERT(direction == RB_DIR_LEFT || direction == RB_DIR_RIGHT); + + if (self == NULL) { +#ifndef RBSMALL + if (RB_SENTINEL_P(rbt->rbt_root)) + return NULL; + return rbt->rbt_minmax[direction]; +#else + self = rbt->rbt_root; + if (RB_SENTINEL_P(self)) + return NULL; + while (!RB_SENTINEL_P(self->rb_nodes[direction])) + self = self->rb_nodes[direction]; + return self; +#endif /* !RBSMALL */ + } + KASSERT(!RB_SENTINEL_P(self)); + /* + * We can't go any further in this direction. We proceed up in the + * opposite direction until our parent is in direction we want to go. + */ + if (RB_SENTINEL_P(self->rb_nodes[direction])) { + while (!RB_ROOT_P(rbt, self)) { + if (other == RB_POSITION(self)) + return RB_FATHER(self); + self = RB_FATHER(self); + } + return NULL; + } + + /* + * Advance down one in current direction and go down as far as possible + * in the opposite direction. + */ + self = self->rb_nodes[direction]; + KASSERT(!RB_SENTINEL_P(self)); + while (!RB_SENTINEL_P(self->rb_nodes[other])) + self = self->rb_nodes[other]; + return self; +} + +static unsigned int +rb_tree_count_black(const struct rb_node *self) +{ + unsigned int left, right; + + if (RB_SENTINEL_P(self)) + return 0; + + left = rb_tree_count_black(self->rb_left); + right = rb_tree_count_black(self->rb_right); + + KASSERT(left == right); + + return left + RB_BLACK_P(self); +} + +static bool +rb_tree_check_node(const struct rb_tree *rbt, const struct rb_node *self, + const struct rb_node *prev, bool red_check) +{ + const rb_tree_ops_t *rbto = rbt->rbt_ops; + rbto_compare_nodes_fn compare_nodes = rbto->rbto_compare_nodes; + + KASSERT(!RB_SENTINEL_P(self)); + KASSERT(prev == NULL || (*compare_nodes)(rbto->rbto_context, + RB_NODETOITEM(rbto, prev), RB_NODETOITEM(rbto, self)) < 0); + + /* + * Verify our relationship to our parent. + */ + if (RB_ROOT_P(rbt, self)) { + KASSERT(self == rbt->rbt_root); + KASSERT(RB_POSITION(self) == RB_DIR_LEFT); + KASSERT(RB_FATHER(self)->rb_nodes[RB_DIR_LEFT] == self); + KASSERT(RB_FATHER(self) == (const struct rb_node *) &rbt->rbt_root); + } else { + int diff = (*compare_nodes)(rbto->rbto_context, + RB_NODETOITEM(rbto, self), + RB_NODETOITEM(rbto, RB_FATHER(self))); + + KASSERT(self != rbt->rbt_root); + KASSERT(!RB_FATHER_SENTINEL_P(self)); + if (RB_POSITION(self) == RB_DIR_LEFT) { + KASSERT(diff < 0); + KASSERT(RB_FATHER(self)->rb_nodes[RB_DIR_LEFT] == self); + } else { + KASSERT(diff > 0); + KASSERT(RB_FATHER(self)->rb_nodes[RB_DIR_RIGHT] == self); + } + } + + /* + * Verify our position in the linked list against the tree itself. + */ + { + const struct rb_node *prev0 = rb_tree_iterate_const(rbt, self, RB_DIR_LEFT); + const struct rb_node *next0 = rb_tree_iterate_const(rbt, self, RB_DIR_RIGHT); + KASSERT(prev0 == TAILQ_PREV(self, rb_node_qh, rb_link)); + KASSERT(next0 == TAILQ_NEXT(self, rb_link)); +#ifndef RBSMALL + KASSERT(prev0 != NULL || self == rbt->rbt_minmax[RB_DIR_LEFT]); + KASSERT(next0 != NULL || self == rbt->rbt_minmax[RB_DIR_RIGHT]); +#endif + } + + /* + * The root must be black. + * There can never be two adjacent red nodes. + */ + if (red_check) { + KASSERT(!RB_ROOT_P(rbt, self) || RB_BLACK_P(self)); + (void) rb_tree_count_black(self); + if (RB_RED_P(self)) { + const struct rb_node *brother; + KASSERT(!RB_ROOT_P(rbt, self)); + brother = RB_FATHER(self)->rb_nodes[RB_POSITION(self) ^ RB_DIR_OTHER]; + KASSERT(RB_BLACK_P(RB_FATHER(self))); + /* + * I'm red and have no children, then I must either + * have no brother or my brother also be red and + * also have no children. (black count == 0) + */ + KASSERT(!RB_CHILDLESS_P(self) + || RB_SENTINEL_P(brother) + || RB_RED_P(brother) + || RB_CHILDLESS_P(brother)); + /* + * If I'm not childless, I must have two children + * and they must be both be black. + */ + KASSERT(RB_CHILDLESS_P(self) + || (RB_TWOCHILDREN_P(self) + && RB_BLACK_P(self->rb_left) + && RB_BLACK_P(self->rb_right))); + /* + * If I'm not childless, thus I have black children, + * then my brother must either be black or have two + * black children. + */ + KASSERT(RB_CHILDLESS_P(self) + || RB_BLACK_P(brother) + || (RB_TWOCHILDREN_P(brother) + && RB_BLACK_P(brother->rb_left) + && RB_BLACK_P(brother->rb_right))); + } else { + /* + * If I'm black and have one child, that child must + * be red and childless. + */ + KASSERT(RB_CHILDLESS_P(self) + || RB_TWOCHILDREN_P(self) + || (!RB_LEFT_SENTINEL_P(self) + && RB_RIGHT_SENTINEL_P(self) + && RB_RED_P(self->rb_left) + && RB_CHILDLESS_P(self->rb_left)) + || (!RB_RIGHT_SENTINEL_P(self) + && RB_LEFT_SENTINEL_P(self) + && RB_RED_P(self->rb_right) + && RB_CHILDLESS_P(self->rb_right))); + + /* + * If I'm a childless black node and my parent is + * black, my 2nd closet relative away from my parent + * is either red or has a red parent or red children. + */ + if (!RB_ROOT_P(rbt, self) + && RB_CHILDLESS_P(self) + && RB_BLACK_P(RB_FATHER(self))) { + const unsigned int which = RB_POSITION(self); + const unsigned int other = which ^ RB_DIR_OTHER; + const struct rb_node *relative0, *relative; + + relative0 = rb_tree_iterate_const(rbt, + self, other); + KASSERT(relative0 != NULL); + relative = rb_tree_iterate_const(rbt, + relative0, other); + KASSERT(relative != NULL); + KASSERT(RB_SENTINEL_P(relative->rb_nodes[which])); +#if 0 + KASSERT(RB_RED_P(relative) + || RB_RED_P(relative->rb_left) + || RB_RED_P(relative->rb_right) + || RB_RED_P(RB_FATHER(relative))); +#endif + } + } + /* + * A grandparent's children must be real nodes and not + * sentinels. First check out grandparent. + */ + KASSERT(RB_ROOT_P(rbt, self) + || RB_ROOT_P(rbt, RB_FATHER(self)) + || RB_TWOCHILDREN_P(RB_FATHER(RB_FATHER(self)))); + /* + * If we are have grandchildren on our left, then + * we must have a child on our right. + */ + KASSERT(RB_LEFT_SENTINEL_P(self) + || RB_CHILDLESS_P(self->rb_left) + || !RB_RIGHT_SENTINEL_P(self)); + /* + * If we are have grandchildren on our right, then + * we must have a child on our left. + */ + KASSERT(RB_RIGHT_SENTINEL_P(self) + || RB_CHILDLESS_P(self->rb_right) + || !RB_LEFT_SENTINEL_P(self)); + + /* + * If we have a child on the left and it doesn't have two + * children make sure we don't have great-great-grandchildren on + * the right. + */ + KASSERT(RB_TWOCHILDREN_P(self->rb_left) + || RB_CHILDLESS_P(self->rb_right) + || RB_CHILDLESS_P(self->rb_right->rb_left) + || RB_CHILDLESS_P(self->rb_right->rb_left->rb_left) + || RB_CHILDLESS_P(self->rb_right->rb_left->rb_right) + || RB_CHILDLESS_P(self->rb_right->rb_right) + || RB_CHILDLESS_P(self->rb_right->rb_right->rb_left) + || RB_CHILDLESS_P(self->rb_right->rb_right->rb_right)); + + /* + * If we have a child on the right and it doesn't have two + * children make sure we don't have great-great-grandchildren on + * the left. + */ + KASSERT(RB_TWOCHILDREN_P(self->rb_right) + || RB_CHILDLESS_P(self->rb_left) + || RB_CHILDLESS_P(self->rb_left->rb_left) + || RB_CHILDLESS_P(self->rb_left->rb_left->rb_left) + || RB_CHILDLESS_P(self->rb_left->rb_left->rb_right) + || RB_CHILDLESS_P(self->rb_left->rb_right) + || RB_CHILDLESS_P(self->rb_left->rb_right->rb_left) + || RB_CHILDLESS_P(self->rb_left->rb_right->rb_right)); + + /* + * If we are fully interior node, then our predecessors and + * successors must have no children in our direction. + */ + if (RB_TWOCHILDREN_P(self)) { + const struct rb_node *prev0; + const struct rb_node *next0; + + prev0 = rb_tree_iterate_const(rbt, self, RB_DIR_LEFT); + KASSERT(prev0 != NULL); + KASSERT(RB_RIGHT_SENTINEL_P(prev0)); + + next0 = rb_tree_iterate_const(rbt, self, RB_DIR_RIGHT); + KASSERT(next0 != NULL); + KASSERT(RB_LEFT_SENTINEL_P(next0)); + } + } + + return true; +} + +void +rb_tree_check(const struct rb_tree *rbt, bool red_check) +{ + const struct rb_node *self; + const struct rb_node *prev; +#ifdef RBSTATS + unsigned int count = 0; +#endif + + KASSERT(rbt->rbt_root != NULL); + KASSERT(RB_LEFT_P(rbt->rbt_root)); + +#if defined(RBSTATS) && !defined(RBSMALL) + KASSERT(rbt->rbt_count > 1 + || rbt->rbt_minmax[RB_DIR_LEFT] == rbt->rbt_minmax[RB_DIR_RIGHT]); +#endif + + prev = NULL; + TAILQ_FOREACH(self, &rbt->rbt_nodes, rb_link) { + rb_tree_check_node(rbt, self, prev, false); +#ifdef RBSTATS + count++; +#endif + } +#ifdef RBSTATS + KASSERT(rbt->rbt_count == count); +#endif + if (red_check) { + KASSERT(RB_BLACK_P(rbt->rbt_root)); + KASSERT(RB_SENTINEL_P(rbt->rbt_root) + || rb_tree_count_black(rbt->rbt_root)); + + /* + * The root must be black. + * There can never be two adjacent red nodes. + */ + TAILQ_FOREACH(self, &rbt->rbt_nodes, rb_link) { + rb_tree_check_node(rbt, self, NULL, true); + } + } +} +#endif /* RBDEBUG */ + +#ifdef RBSTATS +static void +rb_tree_mark_depth(const struct rb_tree *rbt, const struct rb_node *self, + size_t *depths, size_t depth) +{ + if (RB_SENTINEL_P(self)) + return; + + if (RB_TWOCHILDREN_P(self)) { + rb_tree_mark_depth(rbt, self->rb_left, depths, depth + 1); + rb_tree_mark_depth(rbt, self->rb_right, depths, depth + 1); + return; + } + depths[depth]++; + if (!RB_LEFT_SENTINEL_P(self)) { + rb_tree_mark_depth(rbt, self->rb_left, depths, depth + 1); + } + if (!RB_RIGHT_SENTINEL_P(self)) { + rb_tree_mark_depth(rbt, self->rb_right, depths, depth + 1); + } +} + +void +rb_tree_depths(const struct rb_tree *rbt, size_t *depths) +{ + rb_tree_mark_depth(rbt, rbt->rbt_root, depths, 1); +} +#endif /* RBSTATS */ diff --git a/compat/rbtree.h b/compat/rbtree.h new file mode 100644 index 000000000000..656da22a108e --- /dev/null +++ b/compat/rbtree.h @@ -0,0 +1,211 @@ +/* $NetBSD: rbtree.h,v 1.5 2019/03/07 14:39:21 roy Exp $ */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Matt Thomas <matt@3am-software.com>. + * + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +#ifndef _SYS_RBTREE_H_ +#define _SYS_RBTREE_H_ + +#include "config.h" +#include "common.h" + +#if defined(_KERNEL) || defined(_STANDALONE) +#include <sys/types.h> +#else +#include <stdbool.h> +#include <inttypes.h> +#endif +#ifdef HAVE_SYS_QUEUE_H +#include <sys/queue.h> +#else +#include "queue.h" +#endif +#if !defined(__linux__) && !defined(__QNX__) && !defined(__sun) +#include <sys/endian.h> +#else +#include "endian.h" +#endif + +__BEGIN_DECLS + +typedef struct rb_node { + struct rb_node *rb_nodes[2]; +#define RB_DIR_LEFT 0 +#define RB_DIR_RIGHT 1 +#define RB_DIR_OTHER 1 +#define rb_left rb_nodes[RB_DIR_LEFT] +#define rb_right rb_nodes[RB_DIR_RIGHT] + + /* + * rb_info contains the two flags and the parent back pointer. + * We put the two flags in the low two bits since we know that + * rb_node will have an alignment of 4 or 8 bytes. + */ + uintptr_t rb_info; +#define RB_FLAG_POSITION (uintptr_t)0x2 +#define RB_FLAG_RED (uintptr_t)0x1 +#define RB_FLAG_MASK (RB_FLAG_POSITION|RB_FLAG_RED) +#define RB_FATHER(rb) \ + ((struct rb_node *)((rb)->rb_info & ~RB_FLAG_MASK)) +#define RB_SET_FATHER(rb, father) \ + ((void)((rb)->rb_info = (uintptr_t)(father)|((rb)->rb_info & RB_FLAG_MASK))) + +#define RB_SENTINEL_P(rb) ((rb) == NULL) +#define RB_LEFT_SENTINEL_P(rb) RB_SENTINEL_P((rb)->rb_left) +#define RB_RIGHT_SENTINEL_P(rb) RB_SENTINEL_P((rb)->rb_right) +#define RB_FATHER_SENTINEL_P(rb) RB_SENTINEL_P(RB_FATHER((rb))) +#define RB_CHILDLESS_P(rb) \ + (RB_SENTINEL_P(rb) || (RB_LEFT_SENTINEL_P(rb) && RB_RIGHT_SENTINEL_P(rb))) +#define RB_TWOCHILDREN_P(rb) \ + (!RB_SENTINEL_P(rb) && !RB_LEFT_SENTINEL_P(rb) && !RB_RIGHT_SENTINEL_P(rb)) + +#define RB_POSITION(rb) \ + (((rb)->rb_info & RB_FLAG_POSITION) ? RB_DIR_RIGHT : RB_DIR_LEFT) +#define RB_RIGHT_P(rb) (RB_POSITION(rb) == RB_DIR_RIGHT) +#define RB_LEFT_P(rb) (RB_POSITION(rb) == RB_DIR_LEFT) +#define RB_RED_P(rb) (!RB_SENTINEL_P(rb) && ((rb)->rb_info & RB_FLAG_RED) != 0) +#define RB_BLACK_P(rb) (RB_SENTINEL_P(rb) || ((rb)->rb_info & RB_FLAG_RED) == 0) +#define RB_MARK_RED(rb) ((void)((rb)->rb_info |= RB_FLAG_RED)) +#define RB_MARK_BLACK(rb) ((void)((rb)->rb_info &= ~RB_FLAG_RED)) +#define RB_INVERT_COLOR(rb) ((void)((rb)->rb_info ^= RB_FLAG_RED)) +#define RB_ROOT_P(rbt, rb) ((rbt)->rbt_root == (rb)) +#define RB_SET_POSITION(rb, position) \ + ((void)((position) ? ((rb)->rb_info |= RB_FLAG_POSITION) : \ + ((rb)->rb_info &= ~RB_FLAG_POSITION))) +#define RB_ZERO_PROPERTIES(rb) ((void)((rb)->rb_info &= ~RB_FLAG_MASK)) +#define RB_COPY_PROPERTIES(dst, src) \ + ((void)((dst)->rb_info ^= ((dst)->rb_info ^ (src)->rb_info) & RB_FLAG_MASK)) +#define RB_SWAP_PROPERTIES(a, b) do { \ + uintptr_t xorinfo = ((a)->rb_info ^ (b)->rb_info) & RB_FLAG_MASK; \ + (a)->rb_info ^= xorinfo; \ + (b)->rb_info ^= xorinfo; \ + } while (/*CONSTCOND*/ 0) +#ifdef RBDEBUG + TAILQ_ENTRY(rb_node) rb_link; +#endif +} rb_node_t; + +#define RB_TREE_MIN(T) rb_tree_iterate((T), NULL, RB_DIR_LEFT) +#define RB_TREE_MAX(T) rb_tree_iterate((T), NULL, RB_DIR_RIGHT) +#define RB_TREE_NEXT(T, N) rb_tree_iterate((T), (N), RB_DIR_RIGHT) +#define RB_TREE_PREV(T, N) rb_tree_iterate((T), (N), RB_DIR_LEFT) +#define RB_TREE_FOREACH(N, T) \ + for ((N) = RB_TREE_MIN(T); (N); (N) = RB_TREE_NEXT((T), (N))) +#define RB_TREE_FOREACH_REVERSE(N, T) \ + for ((N) = RB_TREE_MAX(T); (N); (N) = RB_TREE_PREV((T), (N))) +#define RB_TREE_FOREACH_SAFE(N, T, S) \ + for ((N) = RB_TREE_MIN(T); \ + (N) && ((S) = RB_TREE_NEXT((T), (N)), 1); \ + (N) = (S)) +#define RB_TREE_FOREACH_REVERSE_SAFE(N, T, S) \ + for ((N) = RB_TREE_MAX(T); \ + (N) && ((S) = RB_TREE_PREV((T), (N)), 1); \ + (N) = (S)) + +#ifdef RBDEBUG +TAILQ_HEAD(rb_node_qh, rb_node); + +#define RB_TAILQ_REMOVE(a, b, c) TAILQ_REMOVE(a, b, c) +#define RB_TAILQ_INIT(a) TAILQ_INIT(a) +#define RB_TAILQ_INSERT_HEAD(a, b, c) TAILQ_INSERT_HEAD(a, b, c) +#define RB_TAILQ_INSERT_BEFORE(a, b, c) TAILQ_INSERT_BEFORE(a, b, c) +#define RB_TAILQ_INSERT_AFTER(a, b, c, d) TAILQ_INSERT_AFTER(a, b, c, d) +#else +#define RB_TAILQ_REMOVE(a, b, c) do { } while (/*CONSTCOND*/0) +#define RB_TAILQ_INIT(a) do { } while (/*CONSTCOND*/0) +#define RB_TAILQ_INSERT_HEAD(a, b, c) do { } while (/*CONSTCOND*/0) +#define RB_TAILQ_INSERT_BEFORE(a, b, c) do { } while (/*CONSTCOND*/0) +#define RB_TAILQ_INSERT_AFTER(a, b, c, d) do { } while (/*CONSTCOND*/0) +#endif /* RBDEBUG */ + +/* + * rbto_compare_nodes_fn: + * return a positive value if the first node > the second node. + * return a negative value if the first node < the second node. + * return 0 if they are considered same. + * + * rbto_compare_key_fn: + * return a positive value if the node > the key. + * return a negative value if the node < the key. + * return 0 if they are considered same. + */ + +typedef signed int (*rbto_compare_nodes_fn)(void *, const void *, const void *); +typedef signed int (*rbto_compare_key_fn)(void *, const void *, const void *); + +typedef struct { + rbto_compare_nodes_fn rbto_compare_nodes; + rbto_compare_key_fn rbto_compare_key; + size_t rbto_node_offset; + void *rbto_context; +} rb_tree_ops_t; + +typedef struct rb_tree { + struct rb_node *rbt_root; + const rb_tree_ops_t *rbt_ops; + struct rb_node *rbt_minmax[2]; +#ifdef RBDEBUG + struct rb_node_qh rbt_nodes; +#endif +#ifdef RBSTATS + unsigned int rbt_count; + unsigned int rbt_insertions; + unsigned int rbt_removals; + unsigned int rbt_insertion_rebalance_calls; + unsigned int rbt_insertion_rebalance_passes; + unsigned int rbt_removal_rebalance_calls; + unsigned int rbt_removal_rebalance_passes; +#endif +} rb_tree_t; + +#ifdef RBSTATS +#define RBSTAT_INC(v) ((void)((v)++)) +#define RBSTAT_DEC(v) ((void)((v)--)) +#else +#define RBSTAT_INC(v) do { } while (/*CONSTCOND*/0) +#define RBSTAT_DEC(v) do { } while (/*CONSTCOND*/0) +#endif + +void rb_tree_init(rb_tree_t *, const rb_tree_ops_t *); +void * rb_tree_insert_node(rb_tree_t *, void *); +void * rb_tree_find_node(rb_tree_t *, const void *); +void * rb_tree_find_node_geq(rb_tree_t *, const void *); +void * rb_tree_find_node_leq(rb_tree_t *, const void *); +void rb_tree_remove_node(rb_tree_t *, void *); +void * rb_tree_iterate(rb_tree_t *, void *, const unsigned int); +#ifdef RBDEBUG +void rb_tree_check(const rb_tree_t *, bool); +#endif +#ifdef RBSTATS +void rb_tree_depths(const rb_tree_t *, size_t *); +#endif + +__END_DECLS + +#endif /* _SYS_RBTREE_H_*/ diff --git a/compat/reallocarray.c b/compat/reallocarray.c new file mode 100644 index 000000000000..9ff9c20b30fb --- /dev/null +++ b/compat/reallocarray.c @@ -0,0 +1,60 @@ +/* $NetBSD: reallocarr.c,v 1.4 2015/08/20 20:08:04 joerg Exp $ */ + +/*- + * Copyright (c) 2015 Joerg Sonnenberger <joerg@NetBSD.org>. + * 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 COPYRIGHT HOLDERS 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 + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <errno.h> +#include <limits.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +/* + * To be clear, this is NetBSD's more refined reallocarr(3) function + * made to look like OpenBSD's more useable reallocarray(3) interface. + */ +#include "reallocarray.h" + +#define SQRT_SIZE_MAX (((size_t)1) << (sizeof(size_t) * CHAR_BIT / 2)) +void * +reallocarray(void *ptr, size_t n, size_t size) +{ + + /* + * Try to avoid division here. + * + * It isn't possible to overflow during multiplication if neither + * operand uses any of the most significant half of the bits. + */ + if ((n | size) >= SQRT_SIZE_MAX && n > SIZE_MAX / size) { + errno = EOVERFLOW; + return NULL; + } + return realloc(ptr, n * size); +} diff --git a/compat/reallocarray.h b/compat/reallocarray.h new file mode 100644 index 000000000000..d855e7b259fb --- /dev/null +++ b/compat/reallocarray.h @@ -0,0 +1,37 @@ +/* $NetBSD: reallocarr.c,v 1.4 2015/08/20 20:08:04 joerg Exp $ */ + +/*- + * Copyright (c) 2015 Joerg Sonnenberger <joerg@NetBSD.org>. + * 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 COPYRIGHT HOLDERS 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 + * COPYRIGHT HOLDERS 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. + */ + +#ifndef REALLOCARRAY_H +#define REALLOCARRAY_H + +void *reallocarray(void *, size_t, size_t); + +#endif diff --git a/compat/setproctitle.c b/compat/setproctitle.c new file mode 100644 index 000000000000..8c03c3922c07 --- /dev/null +++ b/compat/setproctitle.c @@ -0,0 +1,331 @@ +/* + * Copyright © 2010 William Ahern + * Copyright © 2012-2013 Guillem Jover <guillem@hadrons.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <stddef.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <err.h> +#include <unistd.h> +#include <string.h> +//#include "local-link.h" + +#include "config.h" + +static struct { + /* Original value. */ + char *arg0; + + /* Title space available. */ + char *base, *end; + + /* Pointer to original nul character within base. */ + char *nul; + + bool warned; + bool reset; + int error; + + /* Our copy of args and environment to free. */ + int argc; + char **argv; + char **tmp_environ; +} SPT; + + +static inline size_t +spt_min(size_t a, size_t b) +{ + return a < b ? a : b; +} + +/* + * For discussion on the portability of the various methods, see + * https://lists.freebsd.org/pipermail/freebsd-stable/2008-June/043136.html + */ +static int +spt_clearenv(void) +{ +#ifdef HAVE_CLEARENV + return clearenv(); +#else + SPT.tmp_environ = malloc(sizeof(*SPT.tmp_environ)); + if (SPT.tmp_environ == NULL) + return errno; + + SPT.tmp_environ[0] = NULL; + environ = SPT.tmp_environ; + + return 0; +#endif +} + +static int +spt_copyenv(int envc, char *envp[]) +{ + char **envcopy; + char *eq; + size_t envsize; + int i, error; + + if (environ != envp) + return 0; + + /* Make a copy of the old environ array of pointers, in case + * clearenv() or setenv() is implemented to free the internal + * environ array, because we will need to access the old environ + * contents to make the new copy. */ + envsize = (size_t)(envc + 1) * sizeof(char *); + envcopy = malloc(envsize); + if (envcopy == NULL) + return errno; + memcpy(envcopy, envp, envsize); + + error = spt_clearenv(); + if (error) { + environ = envp; + free(envcopy); + return error; + } + + for (i = 0; envcopy[i]; i++) { + eq = strchr(envcopy[i], '='); + if (eq == NULL) + continue; + + *eq = '\0'; + if (setenv(envcopy[i], eq + 1, 1) < 0) + error = errno; + *eq = '='; + + if (error) { +#ifdef HAVE_CLEARENV + /* Because the old environ might not be available + * anymore we will make do with the shallow copy. */ + environ = envcopy; +#else + environ = envp; + free(envcopy); +#endif + return error; + } + } + + /* Dispose of the shallow copy, now that we've finished transfering + * the old environment. */ + free(envcopy); + + return 0; +} + +static int +spt_copyargs(int argc, char *argv[]) +{ + char *tmp; + int i; + + for (i = 1; i < argc || (i >= argc && argv[i]); i++) { + if (argv[i] == NULL) + continue; + + tmp = strdup(argv[i]); + if (tmp == NULL) + return errno; + + argv[i] = tmp; + } + + return 0; +} + +void +setproctitle_init(int argc, char *argv[], char *envp[]) +{ + char *base, *end, *nul, *tmp; + int i, envc, error; + + /* Try to make sure we got called with main() arguments. */ + if (argc < 0) + return; + + base = argv[0]; + if (base == NULL) + return; + + nul = &base[strlen(base)]; + end = nul + 1; + + for (i = 0; i < argc || (i >= argc && argv[i]); i++) { + if (argv[i] == NULL || argv[i] != end) + continue; + + end = argv[i] + strlen(argv[i]) + 1; + } + + for (i = 0; envp[i]; i++) { + if (envp[i] != end) + continue; + + end = envp[i] + strlen(envp[i]) + 1; + } + envc = i; + + SPT.arg0 = strdup(argv[0]); + if (SPT.arg0 == NULL) { + SPT.error = errno; + return; + } + + tmp = strdup(getprogname()); + if (tmp == NULL) { + SPT.error = errno; + return; + } + setprogname(tmp); + + error = spt_copyenv(envc, envp); + if (error) { + SPT.error = error; + return; + } + + error = spt_copyargs(argc, argv); + if (error) { + SPT.error = error; + return; + } + + SPT.argc = argc; + SPT.argv = argv; + + SPT.nul = nul; + SPT.base = base; + SPT.end = end; +} + +void +setproctitle_fini(void) +{ + int i; + + free(SPT.arg0); + SPT.arg0 = NULL; + + for (i = 1; i < SPT.argc; i++) { + if (SPT.argv[i] != NULL) + free(SPT.argv[i]); + } + SPT.argc = 0; + + free(SPT.tmp_environ); + SPT.tmp_environ = NULL; +} + +#ifndef SPT_MAXTITLE +#define SPT_MAXTITLE 255 +#endif + +__printflike(1, 2) static void +setproctitle_impl(const char *fmt, ...) +{ + /* Use buffer in case argv[0] is passed. */ + char buf[SPT_MAXTITLE + 1]; + va_list ap; + char *nul; + int l; + size_t len, base_len; + + if (SPT.base == NULL) { + if (!SPT.warned) { + warnx("setproctitle not initialized, please either call " + "setproctitle_init() or link against libbsd-ctor."); + SPT.warned = true; + } + return; + } + + if (fmt) { + if (fmt[0] == '-') { + /* Skip program name prefix. */ + fmt++; + len = 0; + } else { + /* Print program name heading for grep. */ + l = snprintf(buf, sizeof(buf), "%s: ", getprogname()); + if (l <= 0) + return; + len = (size_t)l; + } + + va_start(ap, fmt); + l = vsnprintf(buf + len, sizeof(buf) - len, fmt, ap); + va_end(ap); + } else { + len = 0; + l = snprintf(buf, sizeof(buf), "%s", SPT.arg0); + } + + if (l <= 0) { + SPT.error = errno; + return; + } + len += (size_t)l; + + base_len = (size_t)(SPT.end - SPT.base); + if (!SPT.reset) { + memset(SPT.base, 0, base_len); + SPT.reset = true; + } else { + memset(SPT.base, 0, spt_min(sizeof(buf), base_len)); + } + + len = spt_min(len, spt_min(sizeof(buf), base_len) - 1); + memcpy(SPT.base, buf, len); + nul = &SPT.base[len]; + + if (nul < SPT.nul) { + *SPT.nul = '.'; + } else if (nul == SPT.nul && &nul[1] < SPT.end) { + *SPT.nul = ' '; + *++nul = '\0'; + } +} +libbsd_symver_default(setproctitle, setproctitle_impl, LIBBSD_0.5); + +/* The original function introduced in 0.2 was a stub, it only got implemented + * in 0.5, make the implementation available in the old version as an alias + * for code linking against that version, and change the default to use the + * new version, so that new code depends on the implemented version. */ +#ifdef HAVE_TYPEOF +extern __typeof__(setproctitle_impl) +setproctitle_stub + __attribute__((__alias__("setproctitle_impl"))); +#else +void +setproctitle_stub(const char *fmt, ...) + __attribute__((__alias__("setproctitle_impl"))); +#endif +libbsd_symver_variant(setproctitle, setproctitle_stub, LIBBSD_0.2); diff --git a/compat/setproctitle.h b/compat/setproctitle.h new file mode 100644 index 000000000000..32dc56bdd38f --- /dev/null +++ b/compat/setproctitle.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2010 William Ahern + * Copyright © 2012-2013 Guillem Jover <guillem@hadrons.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SETPROCTITLE_H +#define SETPROCTITLE_H + +#ifndef __printflike +#if __GNUC__ > 2 || defined(__INTEL_COMPILER) +#define __printflike(a, b) __attribute__((format(printf, a, b))) +#else +#define __printflike(a, b) +#endif +#endif /* !__printflike */ + +/* WEXITSTATUS is defined in stdlib.h which defines free() */ +#ifdef WEXITSTATUS +static inline const char * +getprogname(void) +{ + return "dhcpcd"; +} +static inline void +setprogname(char *name) +{ + free(name); +} +#endif + +void setproctitle_init(int, char *[], char *[]); +__printflike(1, 2) void setproctitle(const char *, ...); +void setproctitle_fini(void); + +#define libbsd_symver_default(alias, symbol, version) \ + extern __typeof(symbol) alias __attribute__((__alias__(#symbol))) + +#define libbsd_symver_variant(alias, symbol, version) +#endif diff --git a/compat/strlcpy.c b/compat/strlcpy.c new file mode 100644 index 000000000000..1fb4a2f9615d --- /dev/null +++ b/compat/strlcpy.c @@ -0,0 +1,51 @@ +/* $OpenBSD: strlcpy.c,v 1.15 2016/10/16 17:37:39 dtucker Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include "strlcpy.h" + +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return (size_t)(src - osrc - 1); /* count does not include NUL */ +} diff --git a/compat/strlcpy.h b/compat/strlcpy.h new file mode 100644 index 000000000000..2131cdec305f --- /dev/null +++ b/compat/strlcpy.h @@ -0,0 +1,24 @@ +/* $OpenBSD: strlcpy.c,v 1.15 2016/10/16 17:37:39 dtucker Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef STRLCPY_H +#define STRLCPY_H + +size_t strlcpy(char *, const char *, size_t); + +#endif diff --git a/compat/strtoi.c b/compat/strtoi.c new file mode 100644 index 000000000000..567d86edde89 --- /dev/null +++ b/compat/strtoi.c @@ -0,0 +1,68 @@ +/* $NetBSD: strtoi.c,v 1.3 2019/11/28 12:33:23 roy Exp $ */ + +/*- + * Copyright (c) 2005 The DragonFly Project. All rights reserved. + * Copyright (c) 2003 Citrus Project, + * 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. + * + * Created by Kamil Rytarowski, based on ID: + * NetBSD: src/common/lib/libc/stdlib/strtoul.c,v 1.3 2008/08/20 19:58:34 oster Exp + */ + +#if defined(HAVE_NBTOOL_CONFIG_H) && HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#ifdef _LIBC +#include "namespace.h" +#endif + +#if defined(_KERNEL) +#include <sys/param.h> +#include <sys/types.h> +#include <lib/libkern/libkern.h> +#elif defined(_STANDALONE) +#include <sys/param.h> +#include <sys/types.h> +#include <lib/libkern/libkern.h> +#include <lib/libsa/stand.h> +#else +#include <stddef.h> +#include <assert.h> +#include <errno.h> +#include <inttypes.h> +#endif + +#include "strtoi.h" + +#define _FUNCNAME strtoi +#define __TYPE intmax_t +#define __WRAPPED strtoimax + +#include "_strtoi.h" + +#ifdef _LIBC +__weak_alias(strtoi, _strtoi) +__weak_alias(strtoi_l, _strtoi_l) +#endif diff --git a/compat/strtoi.h b/compat/strtoi.h new file mode 100644 index 000000000000..bd976fcf3ca3 --- /dev/null +++ b/compat/strtoi.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * Original version ID: + * NetBSD: src/lib/libc/locale/_wcstoul.h,v 1.2 2003/08/07 16:43:03 agc Exp + * + * Created by Kamil Rytarowski, based on ID: + * NetBSD: src/common/lib/libc/stdlib/_strtoul.h,v 1.7 2013/05/17 12:55:56 joerg Exp + */ + +#ifndef STRTOI_H +#define STRTOI_H + +#include <inttypes.h> + +intmax_t strtoi(const char * __restrict nptr, char ** __restrict endptr, + int base, intmax_t lo, intmax_t hi, int *rstatus); +uintmax_t strtou(const char * __restrict nptr, char ** __restrict endptr, + int base, uintmax_t lo, uintmax_t hi, int *rstatus); +#endif diff --git a/compat/strtou.c b/compat/strtou.c new file mode 100644 index 000000000000..f300dcc23817 --- /dev/null +++ b/compat/strtou.c @@ -0,0 +1,68 @@ +/* $NetBSD: strtou.c,v 1.3 2019/11/28 12:33:23 roy Exp $ */ + +/*- + * Copyright (c) 2005 The DragonFly Project. All rights reserved. + * Copyright (c) 2003 Citrus Project, + * 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. + * + * Created by Kamil Rytarowski, based on ID: + * NetBSD: src/common/lib/libc/stdlib/strtoul.c,v 1.3 2008/08/20 19:58:34 oster Exp + */ + +#if defined(HAVE_NBTOOL_CONFIG_H) && HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#ifdef _LIBC +#include "namespace.h" +#endif + +#if defined(_KERNEL) +#include <sys/param.h> +#include <sys/types.h> +#include <lib/libkern/libkern.h> +#elif defined(_STANDALONE) +#include <sys/param.h> +#include <sys/types.h> +#include <lib/libkern/libkern.h> +#include <lib/libsa/stand.h> +#else +#include <stddef.h> +#include <assert.h> +#include <errno.h> +#include <inttypes.h> +#endif + +#include "strtoi.h" + +#define _FUNCNAME strtou +#define __TYPE uintmax_t +#define __WRAPPED strtoumax + +#include "_strtoi.h" + +#ifdef _LIBC +__weak_alias(strtou, _strtou) +__weak_alias(strtou_l, _strtou_l) +#endif diff --git a/config-null.mk b/config-null.mk new file mode 100644 index 000000000000..c7a8de384f49 --- /dev/null +++ b/config-null.mk @@ -0,0 +1,3 @@ +# This space left intentionally blank + +DHCPCD_SRCS+= dhcpcd-embedded.c diff --git a/configure b/configure new file mode 100755 index 000000000000..19eb431816e7 --- /dev/null +++ b/configure @@ -0,0 +1,1814 @@ +#!/bin/sh +# Try and be like autotools configure, but without autotools + +echo "configure args: $*" +exec 3>config.log + +# Ensure that we do not inherit these from env +HOOKSET=false +INET= +ARP= +ARPING= +IPV4LL= +INET6= +PRIVSEP= +PRIVSEP_USER= +ARC4RANDOM= +CLOSEFROM= +RBTREE= +CONSTTIME_MEMEQUAL= +OPEN_MEMSTREAM= +STRLCPY= +UDEV= +OS= +BUILD= +HOST= +HOSTCC= +TARGET= +INCLUDEDIR= +DEBUG= +FORK= +STATIC= +DEVS= +EMBEDDED= +AUTH= +POLL= +SMALL= +SANITIZE=no +STATUSARG= + +DHCPCD_DEFS=dhcpcd-definitions.conf + +for x do + opt=${x%%=*} + var=${x#*=} + case "$opt" in + --os|OS) OS=$var;; + --debug) DEBUG=$var;; + --disable-debug) DEBUG=no;; + --enable-debug) DEBUG=yes;; + --fork) FORK=$var;; + --disable-fork) FORK=no;; + --enable-fork) FORK=yes;; + --disable-static) STATIC=no;; + --enable-static) STATIC=yes;; + --disable-ipv4|--disable-inet) INET=no; ARP=no; ARPING=no; IPV4LL=no;; + --enable-ipv4|--enable-inet) INET=yes;; + --disable-arp) ARP=no; ARPING=no; IPV4LL=no;; + --enable-arp) ARP=yes; INET=yes;; + --disable-arping) ARPING=no;; + --enable-arping) ARPING=yes; ARP=yes; INET=yes;; + --disable-ipv4ll) IPV4LL=no;; + --enable-ipv4ll) IPV4LL=yes; ARP=yes; INET=yes;; + --disable-ipv6|--disable-inet6) INET6=no; DHCP6=no;; + --enable-ipv6|--enable-inet6) INET6=yes;; + --disable-dhcp6) DHCP6=no;; + --enable-dhcp6) DHCP6=yes;; + --disable-embedded) EMBEDDED=no;; + --enable-embedded) EMBEDDED=yes;; + --disable-auth) AUTH=no;; + --enable-auth) AUTH=yes;; + --disable-privsep) PRIVSEP=no;; + --enable-privsep) PRIVSEP=yes;; + --privsepuser) PRIVSEP_USER=$var;; + --prefix) PREFIX=$var;; + --sysconfdir) SYSCONFDIR=$var;; + --bindir|--sbindir) SBINDIR=$var;; + --libexecdir) LIBEXECDIR=$var;; + --statedir|--localstatedir) STATEDIR=$var;; + --dbdir) DBDIR=$var;; + --rundir) RUNDIR=$var;; + --runstatedir) RUNSTATEDIR=$var;; + --mandir) MANDIR=$var;; + --datadir) DATADIR=$var;; + --with-ccopts|CFLAGS) CFLAGS=$var;; + -I|--includedir) INCLUDEDIR="$INCLUDEDIR${INCLUDEDIR:+ }-I$var";; + CC) CC=$var;; + CPPFLAGS) CPPFLAGS=$var;; + PKG_CONFIG) PKG_CONFIG=$var;; + --with-hook) HOOKSCRIPTS="$HOOKSCRIPTS${HOOKSCRIPTS:+ }$var";; + --with-hooks|HOOKSCRIPTS) HOOKSCRIPTS=$var; HOOKSET=true;; + --with-eghook) EGHOOKSCRIPTS="$EGHOOKSCRIPTS${EGHOOKSCRIPTS+ }$var";; + --with-eghooks|EGHOOKSCRIPTS) EGHOOKSCRIPTS=$var; EGHOOKSET=true;; + --with-default-hostname) _DEFAULT_HOSTNAME=$var;; + --build) BUILD=$var;; + --host) HOST=$var; HOSTCC=$var-;; + --target) TARGET=$var;; + --libdir) LIBDIR=$var;; + --without-arc4random) ARC4RANDOM=no;; + --without-strlcpy) STRLCPY=no;; + --without-pidfile_lock) PIDFILE_LOCK=no;; + --without-reallocarrray) REALLOCARRAY=no;; + --without-md5) MD5=no;; + --without-sha2) SHA2=no;; + --without-sha256) SHA2=no;; + --without-hmac) HMAC=no;; + --without-dev) DEV=no;; + --with-udev) DEV=yes; UDEV=yes;; + --without-udev) UDEV=no;; + --with-poll) POLL="$var";; + --sanitise|--sanitize) SANITIZEADDRESS="yes";; + --serviceexists) SERVICEEXISTS=$var;; + --servicecmd) SERVICECMD=$var;; + --servicestatus) SERVICESTATUS=$var;; + --small) SMALL=yes;; + --statusarg) STATUSARG=$var;; + --infodir) ;; # ignore autotools + --disable-maintainer-mode|--disable-dependency-tracking) ;; + --disable-silent-rules) ;; + -V|--version) + v=$(sed -ne 's/.*VERSION[[:space:]]*"\([^"]*\).*/\1/p' defs.h); + c=$(sed -ne 's/^.*copyright\[\] = "\([^"]*\).*/\1/p' dhcpcd.c); + echo "dhcpcd-$v $c"; + exit 0;; + -h|--help) cat <<EOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: configure [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + -V, --version display version information and exit + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX [/] + +By default, \`make install' will install all the files in \'/sbin', +\`/libexec', etc. You can specify +an installation prefix other than \`/' using \`--prefix', +for instance \`--prefix=$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [PREFIX/bin] + --sbindir=DIR system admin executables [PREFIX/sbin] + --libexecdir=DIR program executables [PREFIX/libexec] + --dbdir=DIR database [STATEDIR/db/dhcpcd] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --localstatedir=DIR modifiable single-machine data [/var] + --libdir=DIR object code libraries [PREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --mandir=DIR man documentation [PREFIX/man] + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST build programs to run on HOST [BUILD] + --target=TARGET configure for building compilers for TARGET [HOST] + +Optional Features: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a + nonstandard directory <lib dir> + CPPFLAGS C/C++ preprocessor flags, e.g. -I<include dir> if you have + headers in a nonstandard directory <include dir> + CPP C preprocessor + PKG_CONFIG pkg-config executable + +Use these variables to override the choices made by \`configure' or to help +it to find libraries and programs with nonstandard names/locations. +EOF +exit 0 +;; + *) echo "$0: WARNING: unknown option $opt" >&2;; + esac +done + +: ${SED:=sed} +: ${GREP:=grep} +: ${PKG_CONFIG:=pkg-config} +: ${WC:=wc} + +: ${FORK:=yes} +_which() +{ + x="$(which "$1" 2>/dev/null)" + if [ $? = 0 ] && [ -n "$x" ]; then + echo "$x" + return 0 + fi + for x in /sbin/"$1" /usr/sbin/"$1" \ + /usr/pkg/sbin/"$1" /usr/local/sbin/"$1" + do + if [ -e "$x" ]; then + echo "$x" + return 0 + fi + done + return 1 +} + +CONFIG_H=config.h +CONFIG_MK=config.mk + +if [ -z "$BUILD" ]; then + # autoconf target triplet: cpu-vendor-os + BUILD=$(uname -m)-unknown-$(uname -s | tr '[:upper:]' '[:lower:]') +fi +: ${HOST:=$BUILD} + +if [ -z "$OS" ]; then + echo "Deriving operating system from ... $HOST" + # Derive OS from cpu-vendor-[kernel-]os + CPU=${HOST%%-*} + REST=${HOST#*-} + if [ "$CPU" != "$REST" ]; then + VENDOR=${REST%%-*} + REST=${REST#*-} + if [ "$VENDOR" != "$REST" ]; then + # Use kernel if given, otherwise os + OS=${REST%%-*} + else + # 2 tupple + OS=$VENDOR + VENDOR= + fi + fi + + # Work with cpu-kernel-os, ie Debian + case "$VENDOR" in + linux*|kfreebsd*) OS=$VENDOR; VENDOR= ;; + esac + case "$REST" in + gnu/kfreebsd*) OS="kfreebsd"; VENDOR= ;; + esac + # Special case + case "$OS" in + dragonfly*) + # This means /usr HAS to be mounted not via dhcpcd + : ${LIBEXECDIR:=${PREFIX:-/usr}/libexec} + ;; + gnu*) OS=hurd;; # No HURD support as yet + esac +fi + +echo "Configuring dhcpcd for ... $OS" +rm -f $CONFIG_H $CONFIG_MK +echo "# $OS" >$CONFIG_MK +echo "/* $OS */" >$CONFIG_H + +: ${SYSCONFDIR:=$PREFIX/etc} +: ${SBINDIR:=$PREFIX/sbin} +: ${LIBDIR:=$PREFIX/lib} +: ${LIBEXECDIR:=$PREFIX/libexec} +: ${STATEDIR:=/var} +: ${DBDIR:=$STATEDIR/db/dhcpcd} +: ${RUNSTATEDIR:=$STATEDIR/run} +: ${RUNDIR:=$RUNSTATEDIR/dhcpcd} +: ${MANDIR:=${PREFIX:-/usr}/share/man} +: ${DATADIR:=${PREFIX:-/usr}/share} + +eval SYSCONFDIR="$SYSCONFDIR" +eval LIBDIR="$LIBDIR" +eval LIBEXECDIR="$LIBEXECDIR" +eval STATEDIR="$STATEDIR" +eval DBDIR="$DBDIR" +eval RUNDIR="$RUNDIR" +eval MANDIR="$MANDIR" +eval DATADIR="$DATADIR" + +echo "#ifndef SYSCONFDIR" >>$CONFIG_H +for x in SYSCONFDIR SBINDIR LIBDIR LIBEXECDIR DBDIR RUNDIR; do + eval v=\$$x + # Make files look nice for import + l=$((10 - ${#x})) + unset t + [ $l -gt 3 ] && t=" " + echo "$x=$t $v" >>$CONFIG_MK + unset t + [ $l -gt 2 ] && t=" " + echo "#define $x$t \"$v\"" >>$CONFIG_H +done +echo "#endif" >>$CONFIG_H + +echo "LIBDIR= $LIBDIR" >>$CONFIG_MK +echo "MANDIR= $MANDIR" >>$CONFIG_MK +echo "DATADIR= $DATADIR" >>$CONFIG_MK + +# Always obey CC. +if [ -n "$CC" ]; then + HOSTCC= +else + CC=cc + _COMPILERS="cc clang gcc pcc icc" +fi +# Only look for a cross compiler if --host and --build are not the same +if [ -n "$HOSTCC" ] && [ "$BUILD" != "$HOST" ]; then + for _CC in $_COMPILERS; do + _CC=$(_which "$HOSTCC$_CC") + if [ -x "$_CC" ]; then + CC=$_CC + break + fi + done +fi +if ! type "$CC" >/dev/null 2>&1; then + for _CC in $_COMPILERS; do + _CC=$(_which "$_CC") + if [ -x "$_CC" ]; then + CC=$_CC + break + fi + done +fi + +# Set to blank, then append user config +# We do this so our SED call to append to XCC remains portable +if [ -n "$CFLAGS" ]; then + echo "CFLAGS=" >>$CONFIG_MK + echo "CFLAGS+= $CFLAGS" >>$CONFIG_MK +fi +if [ -n "$CPPFLAGS" ]; then + echo "CPPFLAGS=" >>$CONFIG_MK + echo "CPPFLAGS+= $CPPFLAGS" >>$CONFIG_MK +fi +if [ -n "$INCLUDEDIR" ]; then + echo "CPPFLAGS+= $INCLUDEDIR" >>$CONFIG_MK +fi +if [ -n "$LDFLAGS" ]; then + echo "LDFLAGS=" >>$CONFIG_MK + echo "LDFLAGS+= $LDFLAGS" >>$CONFIG_MK +fi + +echo "CPPFLAGS+= -DHAVE_CONFIG_H" >>$CONFIG_MK + +# NetBSD: Even if we build for $PREFIX, the clueless user might move us to / +LDELF=/libexec/ld.elf_so +if [ -e "$LDELF" ]; then + echo "Linking against $LDELF" + echo "LDFLAGS+= -Wl,-dynamic-linker=$LDELF" >>$CONFIG_MK + echo "LDFLAGS+= -Wl,-rpath=${LIBDIR}" >>$CONFIG_MK +fi + +if [ -z "$PREFIX" ] || [ "$PREFIX" = / ]; then + ALLOW_USR_LIBS=false +else + ALLOW_USR_LIBS=true +fi +case "$OS" in +linux*|solaris*|sunos*|kfreebsd*) ;; +*) + # There might be more than one ... + for LDELFN in /libexec/ld-elf.so.[0-9]*; do + [ -x "$LDELFN" ] && break + done + if ! [ -x "$LDELF" ] || [ -x "$LDELFN" ]; then + if [ -z "$PREFIX" ] || [ "$PREFIX" = "/" ]; then + echo "Forcing a static build for $OS and \$PREFIX of /" + STATIC=yes + ALLOW_USR_LIBS=true + fi + fi + ;; +esac +if [ "$STATIC" = yes ]; then + echo "LDFLAGS+= -static" >>$CONFIG_MK +fi + +if [ -z "$DEBUG" ] && [ -d .git ]; then + printf "Found git checkout ... " + DEBUG=yes +fi +if [ -n "$DEBUG" ] && [ "$DEBUG" != no ] && [ "$DEBUG" != false ]; then + echo "Adding debugging CFLAGS" + + cat <<EOF >>$CONFIG_MK +CFLAGS+= -g -Wall -Wextra -Wundef +CFLAGS+= -Wmissing-prototypes -Wmissing-declarations +CFLAGS+= -Wmissing-format-attribute -Wnested-externs +CFLAGS+= -Winline -Wcast-align -Wcast-qual -Wpointer-arith +CFLAGS+= -Wreturn-type -Wswitch -Wshadow +CFLAGS+= -Wcast-qual -Wwrite-strings +CFLAGS+= -Wformat=2 +CFLAGS+= -Wpointer-sign -Wmissing-noreturn +EOF + + case "$OS" in + mirbsd*|openbsd*);; # OpenBSD has many redundant decs in system headers + bitrig*|solaris*|sunos*) + echo "CFLAGS+= -Wredundant-decls" >>$CONFIG_MK + ;; # Bitrig spouts many conversion errors with htons + # sunos has many as well + *) echo "CFLAGS+= -Wredundant-decls" >>$CONFIG_MK + echo "CFLAGS+= -Wconversion" >>$CONFIG_MK + ;; + esac + + case "$OS" in + solaris*|sunos*);; + *) echo "CFLAGS+= -Wstrict-overflow" >>$CONFIG_MK;; + esac + + # Turn on extra per compiler debugging + case "$CC" in + *gcc*) echo "CFLAGS+= -Wlogical-op" >>$CONFIG_MK;; + esac + + if [ "$SANITIZEADDRESS" = yes ]; then + printf "Testing compiler supports address sanitisation ..." + cat <<EOF >_test.c +int main(void) { + return 0; +} +EOF + if $CC -fsanitize=address _test.c -o _test 2>&3; then + echo "yes" + echo "CFLAGS+= -fsanitize=address" >>$CONFIG_MK + echo "CFLAGS+= -fno-omit-frame-pointer" >>$CONFIG_MK + echo "LDFLAGS+= -fsanitize=address" >>$CONFIG_MK + else + echo "no" + fi + rm -rf _test.c _test + fi +else + echo "CPPFLAGS+= -DNDEBUG" >>$CONFIG_MK +fi + +if [ -n "$FORK" ] && [ "$FORK" != yes ] && [ "$FORK" != true ]; then + echo "There is no fork" + echo "CPPFLAGS+= -DTHERE_IS_NO_FORK" >>$CONFIG_MK +fi + +if [ "$SMALL" = yes ]; then + echo "Building with -DSMALL" + echo "CPPFLAGS+= -DSMALL" >>$CONFIG_MK + DHCPCD_DEFS=dhcpcd-definitions-small.conf + echo "DHCPCD_DEFS= $DHCPCD_DEFS" >>$CONFIG_MK +fi + +case "$OS" in +freebsd*|kfreebsd*) + # FreeBSD hide some newer POSIX APIs behind _GNU_SOURCE ... + echo "CPPFLAGS+= -D_GNU_SOURCE" >>$CONFIG_MK + case "$OS" in + kfreebsd*) echo "CPPFLAGS+= -DBSD" >>$CONFIG_MK;; + esac + echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK + # Whacky includes needed to buck the trend + case "$OS" in + kfreebsd*) echo "#include <inttypes.h>" >>$CONFIG_H; + esac + echo "#include <net/if.h>" >>$CONFIG_H + echo "#include <net/if_var.h>" >>$CONFIG_H + ;; +netbsd*) + # reallocarray(3) is guarded by _OPENBSD_SOURCE + echo "CPPFLAGS+= -D_OPENBSD_SOURCE" >>$CONFIG_MK + echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK + ;; +linux*) + echo "CPPFLAGS+= -D_GNU_SOURCE" >>$CONFIG_MK + # Large File Support, should be fine for 32-bit systems. + # But if this is the case, why is it not set by default? + echo "CPPFLAGS+= -D_FILE_OFFSET_BITS=64" >>$CONFIG_MK + echo "CPPFLAGS+= -D_LARGEFILE_SOURCE" >>$CONFIG_MK + echo "CPPFLAGS+= -D_LARGEFILE64_SOURCE" >>$CONFIG_MK + echo "DHCPCD_SRCS+= if-linux.c" >>$CONFIG_MK + # for RTM_NEWADDR and friends + echo "#include <asm/types.h> /* fix broken headers */" >>$CONFIG_H + echo "#include <sys/socket.h> /* fix broken headers */" >>$CONFIG_H + echo "#include <linux/rtnetlink.h>" >>$CONFIG_H + # cksum does't support -a and netpgp is rare + echo "CKSUM= sha256sum --tag" >>$CONFIG_MK + echo "PGP= gpg2" >>$CONFIG_MK + ;; +qnx*) + echo "CPPFLAGS+= -D__EXT" >>$CONFIG_MK + echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK + ;; +solaris*|sunos*) + echo "CPPFLAGS+= -D_XPG4_2 -D__EXTENSIONS__ -DBSD_COMP" \ + >>$CONFIG_MK + echo "DHCPCD_SRCS+= if-sun.c" >>$CONFIG_MK + echo "LDADD+= -ldlpi -lkstat" >>$CONFIG_MK + ;; +*) + echo "DHCPCD_SRCS+= if-bsd.c" >>$CONFIG_MK + ;; +esac + +if [ -n "${_DEFAULT_HOSTNAME+x}" ]; then + DEFAULT_HOSTNAME="${_DEFAULT_HOSTNAME}" +else + case "$OS" in + linux*) DEFAULT_HOSTNAME="(none)";; + *) DEFAULT_HOSTNAME="";; + esac +fi +echo "DEFAULT_HOSTNAME= $DEFAULT_HOSTNAME" >>$CONFIG_MK + +if [ -z "$INET" ] || [ "$INET" = yes ]; then + echo "Enabling INET support" + echo "CPPFLAGS+= -DINET" >>$CONFIG_MK + echo "DHCPCD_SRCS+= dhcp.c ipv4.c bpf.c" >>$CONFIG_MK + if [ -z "$ARP" ] || [ "$ARP" = yes ]; then + echo "Enabling ARP support" + echo "CPPFLAGS+= -DARP" >>$CONFIG_MK + echo "DHCPCD_SRCS+= arp.c" >>$CONFIG_MK + fi + if [ -z "$ARPING" ] || [ "$ARPING" = yes ]; then + echo "Enabling ARPing support" + echo "CPPFLAGS+= -DARPING" >>$CONFIG_MK + fi + if [ -z "$IPV4LL" ] || [ "$IPV4LL" = yes ]; then + echo "Enabling IPv4LL support" + echo "CPPFLAGS+= -DIPV4LL" >>$CONFIG_MK + echo "DHCPCD_SRCS+= ipv4ll.c" >>$CONFIG_MK + fi +fi +if [ -z "$INET6" ] || [ "$INET6" = yes ]; then + echo "Enabling INET6 support" + echo "CPPFLAGS+= -DINET6" >>$CONFIG_MK + echo "DHCPCD_SRCS+= ipv6.c ipv6nd.c" >>$CONFIG_MK + if [ -z "$DHCP6" ] || [ "$DHCP6" = yes ]; then + echo "Enabling DHCPv6 support" + echo "CPPFLAGS+= -DDHCP6" >>$CONFIG_MK + echo "DHCPCD_SRCS+= dhcp6.c" >>$CONFIG_MK + fi +fi +if [ -z "$AUTH" ] || [ "$AUTH" = yes ]; then + echo "Enabling Authentication" + echo "CPPFLAGS+= -DAUTH" >>$CONFIG_MK + echo "SRCS+= auth.c" >>$CONFIG_MK +fi + +if [ -z "$PRIVSEP" ]; then + # privilege separation works fine .... except on Solaris + case "$OS" in + solaris*|sunos*) PRIVSEP=no;; + *) PRIVSEP=yes;; + esac +fi + +if [ "$PRIVSEP" = yes ]; then + echo "Enabling Privilege Separation" + + # Try and work out system user + if [ -z "$PRIVSEP_USER" ]; then + printf "Detecting a suitable user for dhcpcd ... " + for x in _dhcpcd _dhcp dhcpcd; do + home=$(getent passwd $x 2>/dev/null | cut -d: -f6) + if [ -d "$home" ]; then + PRIVSEP_USER="$x" + break + fi + done + fi + if [ -n "$PRIVSEP_USER" ]; then + echo "$PRIVSEP_USER" + else + PRIVSEP_USER=dhcpcd + echo + echo "No suitable user found for Priviledge Separation!" + fi + + echo "CPPFLAGS+= -DPRIVSEP" >>$CONFIG_MK + echo "PRIVSEP_USER?= $PRIVSEP_USER" >>$CONFIG_MK + echo "#ifndef PRIVSEP_USER" >>$CONFIG_H + echo "#define PRIVSEP_USER \"$PRIVSEP_USER\"" >>$CONFIG_H + echo "#endif" >>$CONFIG_H + echo "PRIVSEP_SRCS= privsep.c privsep-root.c" >>$CONFIG_MK + echo "PRIVSEP_SRCS+= privsep-control.c privsep-inet.c" >>$CONFIG_MK + if [ -z "$INET" ] || [ "$INET" = yes ]; then + echo "PRIVSEP_SRCS+= privsep-bpf.c" >>$CONFIG_MK + fi + case "$OS" in + linux*) echo "PRIVSEP_SRCS+= privsep-linux.c" >>$CONFIG_MK;; + solaris*|sunos*) echo "PRIVSEP_SRCS+= privsep-sun.c" >>$CONFIG_MK;; + *) echo "PRIVSEP_SRCS+= privsep-bsd.c" >>$CONFIG_MK;; + esac +else + echo "PRIVSEP_SRCS=" >>$CONFIG_MK +fi + +echo "Using compiler .. $CC" +# Add CPPFLAGS and CFLAGS to CC for testing features +XCC="$CC `$SED -n -e 's/CPPFLAGS+=*\(.*\)/\1/p' $CONFIG_MK`" +XCC="$XCC `$SED -n -e 's/CFLAGS+=*\(.*\)/\1/p' $CONFIG_MK`" + +# When running tests, treat all warnings as errors. +# This avoids the situation where we link to a libc symbol +# without the correct header because it might be hidden behind +# a _*_SOURCE #define guard. +XCC="$XCC -Wall -Werror" + +# Now test we can use the compiler with our CFLAGS +cat <<EOF >_test.c +int main(void) { + return 0; +} +EOF +_CC=false +if $XCC _test.c -o _test >/dev/null 2>&3; then + [ -x _test ] && _CC=true +fi +rm -f _test.c _test +if ! $_CC; then + echo $XCC + echo "$CC does not create executables" >&2 + exit 1 +fi +[ "$CC" != cc ] && echo "CC= $CC" >>$CONFIG_MK +$CC --version | $SED -e '1!d' + +if [ "$PRIVSEP" = yes ]; then + printf "Testing for capsicum ... " + cat <<EOF >_capsicum.c +#include <sys/capsicum.h> +int main(void) { + return cap_enter(); +} +EOF + if $XCC _capsicum.c -o _capsicum 2>&3; then + echo "yes" + echo "#define HAVE_CAPSICUM" >>$CONFIG_H + else + echo "no" + fi + rm -f _capsicum.c _capsicum + + printf "Testing for pledge ... " + cat <<EOF >_pledge.c +#include <unistd.h> +int main(void) { + return pledge("stdio", NULL); +} +EOF + if $XCC _pledge.c -o _pledge 2>&3; then + echo "yes" + echo "#define HAVE_PLEDGE" >>$CONFIG_H + else + echo "no" + fi + rm -f _pledge.c _pledge +fi + +# This block needs to be after the compiler test due to embedded quotes. +if [ -z "$EMBEDDED" ] || [ "$EMBEDDED" = yes ]; then + echo "$DHCPCD_DEFS will be embedded in dhcpcd itself" + echo "DHCPCD_SRCS+= dhcpcd-embedded.c" >>$CONFIG_MK +else + echo "$DHCPCD_DEFS will be installed to $LIBEXECDIR" + echo "CPPFLAGS+= -DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK + echo "EMBEDDEDINSTALL= _embeddedinstall" >>$CONFIG_MK +fi + +if [ "$OS" = linux ]; then + printf "Testing for nl80211 ... " + cat <<EOF >_nl80211.c +#include <linux/nl80211.h> +int main(void) { + return 0; +} +EOF + if $XCC _nl80211.c -o _nl80211 2>&3; then + echo "yes" + echo "#define HAVE_NL80211_H" >>$CONFIG_H + else + echo "no" + echo "DHCPCD_SRCS+= if-linux-wext.c" >>$CONFIG_MK + fi + rm -f _nl80211.c _nl80211 + + printf "Testing for IN6_ADDR_GEN_MODE_NONE ... " + cat <<EOF >_IN6_ADDR_GEN_MODE_NONE.c +#include <linux/if_link.h> +int main(void) { + int x = IN6_ADDR_GEN_MODE_NONE; + return x; +} +EOF + if $XCC _IN6_ADDR_GEN_MODE_NONE.c -o _IN6_ADDR_GEN_MODE_NONE 2>&3; then + echo "yes" + echo "#define HAVE_IN6_ADDR_GEN_MODE_NONE" >>$CONFIG_H + else + echo "no" + fi + rm -f _IN6_ADDR_GEN_MODE_NONE.c _IN6_ADDR_GEN_MODE_NONE +else + printf "Testing for ifam_pid ... " + cat <<EOF >_ifam_pid.c +#include <net/if.h> +int main(void) { + struct ifa_msghdr ifam = { }; + return (int)ifam.ifam_pid; +} +EOF + if $XCC _ifam_pid.c -o _ifam_pid 2>&3; then + echo "yes" + echo "#define HAVE_IFAM_PID" >>$CONFIG_H + else + echo "no" + fi + rm -f _ifam_pid.c _ifam_pid + + printf "Testing for ifam_addrflags ... " + cat <<EOF >_ifam_addrflags.c +#include <net/if.h> +int main(void) { + struct ifa_msghdr ifam = { }; + return (int)ifam.ifam_addrflags; +} +EOF + if $XCC _ifam_addrflags.c -o _ifam_addrflags 2>&3; then + echo "yes" + echo "#define HAVE_IFAM_ADDRFLAGS" >>$CONFIG_H + else + echo "no" + fi + rm -f _ifam_addrflags.c _ifam_addrflags +fi + +abort=false +# We require the libc to support non standard functions, like getifaddrs +printf "Testing for getifaddrs ... " +cat <<EOF >_getifaddrs.c +#include <sys/types.h> +#include <ifaddrs.h> +int main(void) { + struct ifaddrs *ifap; + return getifaddrs(&ifap); +} +EOF +LIBSOCKET= +if $XCC _getifaddrs.c -o _getifaddrs 2>&3; then + echo "yes" +elif $XCC _getifaddrs.c -o _getifaddrs -lsocket 2>&3; then + LIBSOCKET=-lsocket + echo "yes (-lsocket)" + echo "LDADD+= -lsocket" >>$CONFIG_MK +else + echo "no" + echo "libc support for getifaddrs is required - aborting" >&2 + abort=true +fi +rm -f _getifaddrs.c _getifaddrs +$abort && exit 1 + +printf "Testing for ifaddrs.ifa_addrflags ... " +cat <<EOF >_getifaddrs_addrflags.c +#include <sys/types.h> +#include <ifaddrs.h> +int main(void) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + return (int)ifap->ifa_addrflags; +} +EOF +if $XCC _getifaddrs_addrflags.c -o _getifaddrs_addrflags $LIBSOCKET 2>&3; then + echo "yes" + echo "#define HAVE_IFADDRS_ADDRFLAGS" >>$CONFIG_H +else + echo "no" +fi +rm -f _getifaddrs_addrflags.c _getifaddrs_addrflags + +printf "Testing for clock_gettime ... " +cat <<EOF >_clock_gettime.c +#include <time.h> +int main(void) { + struct timespec ts; + return clock_gettime(CLOCK_MONOTONIC, &ts); +} +EOF +if $XCC _clock_gettime.c -o _clock_gettime 2>&3; then + echo "yes" +elif $XCC _clock_gettime.c -lrt -o _clock_gettime 2>&3; then + echo "yes (-lrt)" + echo "LDADD+= -lrt" >>$CONFIG_MK +else + echo "no" + echo "libc support for clock_getttime is required - aborting" >&2 + abort=true +fi +rm -f _clock_gettime.c _clock_gettime +$abort && exit 1 + +printf "Testing ioctl request type ... " +cat <<EOF >_ioctl.c +#include <sys/ioctl.h> +int main(void) { + unsigned long req = 0; + return ioctl(3, req, &req); +} +EOF +if $XCC _ioctl.c -o _ioctl 2>&3; then + IOCTL_REQ="unsigned long" +else + IOCTL_REQ="int" +fi +echo "$IOCTL_REQ" +# Our default is unsigned long +# We can still define it, but it makes the code path slightly bigger +if [ "$IOCTL_REQ" != "unsigned long" ]; then + echo "#define IOCTL_REQUEST_TYPE $IOCTL_REQ" >>$CONFIG_H +fi +rm -f _ioctl.c _ioctl + +printf "Testing for inet_ntoa ... " +cat <<EOF >_inet_ntoa.c +#include <netinet/in.h> +#include <arpa/inet.h> +int main(void) { + struct in_addr in = { .s_addr = 0 }; + inet_ntoa(in); + return 0; +} +EOF +if $XCC _inet_ntoa.c -o _inet_ntoa 2>&3; then + echo "yes" +elif $XCC _inet_ntoa.c -lnsl -o _inet_ntoa 2>&3; then + echo "yes (-lnsl)" + echo "LDADD+= -lnsl" >>$CONFIG_MK +elif $XCC _inet_ntoa.c -lsocket -o _inet_ntoa 2>&3; then + echo "yes (-lsocket)" + echo "LDADD+= -lsocket" >>$CONFIG_MK +else + echo "no" + echo "libc support for inet_ntoa is required - aborting" >&2 + abort=true +fi +rm -f _inet_ntoa.c _inet_ntoa +$abort && exit 1 + +if [ -z "$ARC4RANDOM" ]; then + printf "Testing for arc4random ... " + cat <<EOF >_arc4random.c +#include <stdlib.h> +int main(void) { + arc4random(); + return 0; +} +EOF + if $XCC _arc4random.c -o _arc4random 2>&3; then + ARC4RANDOM=yes + else + ARC4RANDOM=no + fi + echo "$ARC4RANDOM" + rm -f _arc4random.c _arc4random +fi +if [ "$ARC4RANDOM" = no ]; then + echo "COMPAT_SRCS+= compat/arc4random.c" >>$CONFIG_MK + echo "#include \"compat/arc4random.h\"" >>$CONFIG_H +fi + +if [ -z "$ARC4RANDOM_UNIFORM" ]; then + printf "Testing for arc4random_uniform ... " + cat <<EOF >_arc4random_uniform.c +#include <stdlib.h> +int main(void) { + arc4random_uniform(100); + return 0; +} +EOF + if $XCC _arc4random_uniform.c -o _arc4random_uniform 2>&3; then + ARC4RANDOM_UNIFORM=yes + else + ARC4RANDOM_UNIFORM=no + fi + echo "$ARC4RANDOM_UNIFORM" + rm -f _arc4random_uniform.c _arc4random_uniform +fi +if [ "$ARC4RANDOM_UNIFORM" = no ]; then + echo "COMPAT_SRCS+= compat/arc4random_uniform.c" >>$CONFIG_MK + echo "#include \"compat/arc4random_uniform.h\"" >>$CONFIG_H +fi + +if [ -z "$OPEN_MEMSTREAM" ]; then + printf "Testing for open_memstream ... " + cat <<EOF >_open_memstream.c +#include <stdio.h> +int main(void) { + return open_memstream(NULL, NULL) != NULL ? 0 : 1; +} +EOF + if $XCC _open_memstream.c -o _open_memstream 2>&3; then + OPEN_MEMSTREAM=yes + else + OPEN_MEMSTREAM=no + fi + echo "$OPEN_MEMSTREAM" + rm -f _open_memstream.c _open_memstream +fi +if [ "$OPEN_MEMSTREAM" = yes ]; then + echo "#define HAVE_OPEN_MEMSTREAM" >>$CONFIG_H +elif [ "$PRIVSEP" = yes ]; then + echo "WARNING: Ensure that /tmp exists in the privsep users chroot" +fi + +if [ -z "$PIDFILE_LOCK" ]; then + printf "Testing for pidfile_lock ... " + cat <<EOF >_pidfile.c +#include <stdlib.h> +#include <util.h> +int main(void) { + pidfile_lock(NULL); + return 0; +} +EOF + # We only want to link to libutil if it exists in /lib + if $ALLOW_USR_LIBS; then + set -- / + else + set -- $(ls /lib/libutil.so.* 2>/dev/null) + fi + if $XCC _pidfile.c -o _pidfile 2>&3; then + PIDFILE_LOCK=yes + elif [ -e "$1" ] && $XCC _pidfile.c -o _pidfile -lutil 2>&3; then + PIDFILE_LOCK="yes (-lutil)" + LIBUTIL="-lutil" + else + PIDFILE_LOCK=no + fi + echo "$PIDFILE_LOCK" + rm -f _pidfile.c _pidfile +fi +if [ "$PIDFILE_LOCK" = no ]; then + echo "COMPAT_SRCS+= compat/pidfile.c" >>$CONFIG_MK + echo "#include \"compat/pidfile.h\"" >>$CONFIG_H +else + echo "#define HAVE_UTIL_H" >>$CONFIG_H + if [ -n "$LIBUTIL" ]; then + echo "LDADD+= $LIBUTIL" >>$CONFIG_MK + fi +fi + +if [ -z "$SETPROCTITLE" ]; then + printf "Testing for setproctitle ... " + cat << EOF >_setproctitle.c +#include <stdlib.h> +#include <unistd.h> +int main(void) { + setproctitle("foo"); + return 0; +} +EOF + if $XCC _setproctitle.c -o _setproctitle 2>&3; then + SETPROCTITLE=yes + else + SETPROCTITLE=no + fi + echo "$SETPROCTITLE" + rm -f _setproctitle.c _setproctitle +fi +if [ "$SETPROCTITLE" = no ]; then + case "$OS" in + solaris*|sunos*) + echo "$OS has no support for setting process title" + echo "#define setproctitle(...)" >>$CONFIG_H + ;; + *) + echo "COMPAT_SRCS+= compat/setproctitle.c" >>$CONFIG_MK + echo "#include \"compat/setproctitle.h\"" \ + >>$CONFIG_H + ;; + esac +fi + +if [ -z "$STRLCPY" ]; then + printf "Testing for strlcpy ... " + cat <<EOF >_strlcpy.c +#include <string.h> +int main(void) { + const char s1[] = "foo"; + char s2[10]; + strlcpy(s2, s1, sizeof(s2)); + return 0; +} +EOF + if $XCC _strlcpy.c -o _strlcpy 2>&3; then + STRLCPY=yes + else + STRLCPY=no + fi + echo "$STRLCPY" + rm -f _strlcpy.c _strlcpy +fi +if [ "$STRLCPY" = no ]; then + echo "COMPAT_SRCS+= compat/strlcpy.c" >>$CONFIG_MK + echo "#include \"compat/strlcpy.h\"" >>$CONFIG_H +fi + +if [ -z "$STRTOI" ]; then + printf "Testing for strtoi ... " + cat <<EOF >_strtoi.c +#include <stdlib.h> +#include <limits.h> +#include <inttypes.h> +int main(void) { + int e; + strtoi("1234", NULL, 0, 0, INT32_MAX, &e); + return 0; +} +EOF + if $XCC _strtoi.c -o _strtoi 2>&3; then + STRTOI=yes + else + STRTOI=no + fi + echo "$STRTOI" + rm -f _strtoi.c _strtoi +fi +if [ "$STRTOI" = no ]; then + echo "COMPAT_SRCS+= compat/strtoi.c compat/strtou.c" >>$CONFIG_MK + echo "#include \"compat/strtoi.h\"" >>$CONFIG_H +fi + +if [ -z "$CONSTTIME_MEMEQUAL" ]; then + printf "Testing for consttime_memequal ... " + cat <<EOF >_consttime_memequal.c +#include <string.h> +int main(void) { + return consttime_memequal("deadbeef", "deadbeef", 8); +} +EOF + if $XCC _consttime_memequal.c -o _consttime_memequal 2>&3; then + CONSTTIME_MEMEQUAL=yes + else + CONSTTIME_MEMEQUAL=no + fi + echo "$CONSTTIME_MEMEQUAL" + rm -f _consttime_memequal.c _consttime_memequal +fi +if [ "$CONSTTIME_MEMEQUAL" = no ]; then + echo "#include \"compat/consttime_memequal.h\"" \ + >>$CONFIG_H +fi + +if [ -z "$DPRINTF" ]; then + printf "Testing for dprintf ... " + cat <<EOF >_dprintf.c +#include <stdio.h> +int main(void) { + return dprintf(0, "%d", 0); +} +EOF + if $XCC _dprintf.c -o _dprintf 2>&3; then + DPRINTF=yes + else + DPRINTF=no + fi + echo "$DPRINTF" + rm -f _dprintf.c _dprintf +fi +if [ "$DPRINTF" = no ]; then + echo "COMPAT_SRCS+= compat/dprintf.c" >>$CONFIG_MK + echo "#include \"compat/dprintf.h\"" >>$CONFIG_H +fi + +if [ -z "$TAILQ_FOREACH_SAFE" ]; then + printf "Testing for TAILQ_FOREACH_SAFE ... " + cat <<EOF >_queue.c +#include <sys/queue.h> +int main(void) { +#ifndef TAILQ_FOREACH_SAFE +#error TAILQ_FOREACH_SAFE +#endif + return 0; +} +EOF + if $XCC _queue.c -o _queue 2>&3; then + TAILQ_FOREACH_SAFE=yes + TAILQ_FOREACH=yes + else + TAILQ_FOREACH_SAFE=no + fi + echo "$TAILQ_FOREACH_SAFE" + rm -f _queue.c _queue +fi +if [ "$TAILQ_FOREACH_SAFE" = no ] && [ -z "$TAILQ_FOREACH_MUTABLE" ]; then + printf "Testing for TAILQ_FOREACH_MUTABLE ... " + cat <<EOF >_queue.c +#include <sys/queue.h> +int main(void) { +#ifndef TAILQ_FOREACH_MUTABLE +#error TAILQ_FOREACH_MUTABLE +#endif + return 0; +} +EOF + if $XCC _queue.c -o _queue 2>&3; then + TAILQ_FOREACH_MUTABLE=yes + TAILQ_FOREACH_SAFE=yes + TAILQ_FOREACH=yes + echo "#define TAILQ_FOREACH_SAFE TAILQ_FOREACH_MUTABLE" \ + >> $CONFIG_H + else + TAILQ_FOREACH_MUTABLE=no + fi + echo "$TAILQ_FOREACH_MUTABLE" + rm -f _queue.c _queue +fi + + +if [ -z "$TAILQ_CONCAT" ]; then + printf "Testing for TAILQ_CONCAT ..." + cat <<EOF >_queue.c +#include <sys/queue.h> +int main(void) { +#ifndef TAILQ_CONCAT +#error TAILQ_CONCAT +#endif + return 0; +} +EOF + if $XCC _queue.c -o _queue 2>&3; then + TAILQ_CONCAT=yes + TAILQ_FOREACH=yes + else + TAILQ_CONCAT=no + fi + echo "$TAILQ_CONCAT" + rm -f _queue.c _queue +fi + +if [ -z "$TAILQ_FOREACH" ]; then + printf "Testing for TAILQ_FOREACH ... " + cat <<EOF >_queue.c +#include <sys/queue.h> +int main(void) { +#ifndef TAILQ_FOREACH +#error TAILQ_FOREACH +#endif + return 0; +} +EOF + if $XCC _queue.c -o _queue 2>&3; then + TAILQ_FOREACH=yes + else + TAILQ_FOREACH=no + fi + echo "$TAILQ_FOREACH" + rm -f _queue.c _queue +fi + +if [ "$TAILQ_FOREACH_SAFE" = no ] || [ "$TAILQ_CONCAT" = no ]; then + # If we don't include sys/queue.h then clang analyser finds + # too many false positives. + # See http://llvm.org/bugs/show_bug.cgi?id=18222 + # Strictly speaking this isn't needed, but I like it to help + # catch any nasties. + if [ "$TAILQ_FOREACH" = yes ]; then + echo "#include <sys/queue.h>">>$CONFIG_H + fi + echo "#include \"compat/queue.h\"">>$CONFIG_H +else + echo "#define HAVE_SYS_QUEUE_H" >>$CONFIG_H +fi + +if [ -z "$RBTREE" ]; then + printf "Testing for rb_tree_init ... " + cat <<EOF >_rbtree.c +#include <sys/rbtree.h> +static rb_tree_ops_t ops; +int main(void) { + rb_tree_t tree; + rb_tree_init(&tree, &ops); + return 0; +} +EOF + if $XCC _rbtree.c -o _rbtree 2>&3; then + RBTREE=yes + else + RBTREE=no + fi + echo "$RBTREE" + rm -f _rbtree.c _rbtree +fi +if [ "$RBTREE" = no ]; then + echo "#define RBTEST" >>$CONFIG_H + echo "COMPAT_SRCS+= compat/rb.c" >>$CONFIG_MK + echo "#include \"compat/rbtree.h\"" >>$CONFIG_H +else + echo "#define HAVE_SYS_RBTREE_H" >>$CONFIG_H +fi + +if [ -z "$REALLOCARRAY" ]; then + printf "Testing for reallocarray ... " + cat <<EOF >_reallocarray.c +#include <stdlib.h> + +int main(void) { + void *foo = reallocarray(NULL, 0, 0); + return foo == NULL ? 1 : 0; +} +EOF + if $XCC _reallocarray.c -o _reallocarray 2>&3; then + REALLOCARRAY=yes + else + REALLOCARRAY=no + fi + echo "$REALLOCARRAY" + rm -f _reallocarray.c _reallocarray +fi +if [ "$REALLOCARRAY" = no ]; then + echo "COMPAT_SRCS+= compat/reallocarray.c" >>$CONFIG_MK + echo "#include \"compat/reallocarray.h\"">>$CONFIG_H +fi +# Set this for eloop +echo "#define HAVE_REALLOCARRAY" >>$CONFIG_H + +if [ -z "$POLL" ]; then + printf "Testing for ppoll ... " + cat <<EOF >_ppoll.c +#include <poll.h> +#include <stddef.h> +int main(void) { + struct pollfd fds; + return ppoll(&fds, 1, NULL, NULL); +} +EOF + if $XCC _ppoll.c -o _ppoll 2>&3; then + POLL=ppoll + echo "yes" + else + echo "no" + fi + rm -f _ppoll.c _ppoll +fi +if [ -z "$POLL" ]; then + printf "Testing for pollts ... " + cat <<EOF >_pollts.c +#include <poll.h> +#include <stddef.h> +int main(void) { + struct pollfd fds; + return pollts(&fds, 1, NULL, NULL); +} +EOF + if $XCC _pollts.c -o _pollts 2>&3; then + POLL=pollts + echo "yes" + else + echo "no" + fi + rm -f _pollts.c _pollts +fi +if [ -z "$POLL" ]; then + printf "Testing for pselect ... " + cat <<EOF >_pselect.c +#include <sys/select.h> +#include <stdlib.h> +int main(void) { + pselect(0, NULL, NULL, NULL, NULL, NULL); + return 0; +} +EOF + if $XCC _pselect.c -o _pselect 2>&3; then + POLL=pselect + echo "yes" + else + echo "no" + fi + rm -f _pselect.c _pselect +fi +case "$POLL" in +ppoll) + echo "#define HAVE_PPOLL" >>$CONFIG_H + ;; +pollts) + echo "#define HAVE_POLLTS" >>$CONFIG_H + ;; +pselect) + echo "#define HAVE_PSELECT" >>$CONFIG_H + ;; +*) + echo "No suitable polling function is available, not even pselect" >&2 + exit 1 + ;; +esac + +if [ -z "$BE64ENC" ]; then + printf "Testing for be64enc ... " + cat <<EOF >_be64enc.c +#include <sys/endian.h> +#include <stdlib.h> +int main(void) { + be64enc(NULL, 0); + return 0; +} +EOF + if $XCC _be64enc.c -o _be64enc 2>&3; then + BE64ENC=yes + else + BE64ENC=no + fi + echo "$BE64ENC" + rm -f _be64enc.c _be64enc +fi +if [ "$BE64ENC" = no ]; then + echo "#include \"compat/endian.h\"" >>$CONFIG_H +fi + +if [ -z "$FLS64" ]; then + printf "Testing for fls64 ... " + cat <<EOF >_fls64.c +#include <sys/bitops.h> +int main(void) { + return (int)fls64(1337); +} +EOF + if $XCC _fls64.c -o _fls64 2>&3; then + FLS64=yes + else + FLS64=no + fi + echo "$FLS64" + rm -f _fls64.c _fls64 +fi +if [ "$FLS64" = yes ]; then + echo "#define HAVE_SYS_BITOPS_H" >>$CONFIG_H +fi + +# Workaround for DragonFlyBSD import +if [ "$OS" = dragonfly ]; then + echo "#ifdef USE_PRIVATECRYPTO" >>$CONFIG_H + echo "#define HAVE_MD5_H" >>$CONFIG_H + echo "#define SHA2_H <openssl/sha.h>" >>$CONFIG_H + echo "#else" >>$CONFIG_H +fi + +if [ -z "$MD5" ]; then + MD5_LIB= + printf "Testing for MD5Init ... " + cat <<EOF >_md5.c +#include <sys/types.h> +#include <md5.h> +#include <stdlib.h> +int main(void) { + MD5_CTX context; + MD5Init(&context); + return 0; +} +EOF + # We only want to link to libmd if it exists in /lib + if $ALLOW_USR_LIBS; then + set -- / + else + set -- $(ls /lib/libmd.so.* 2>/dev/null) + fi + if $XCC _md5.c -o _md5 2>&3; then + MD5=yes + elif [ -e "$1" ] && $XCC _md5.c -lmd -o _md5 2>&3; then + MD5="yes (-lmd)" + MD5_LIB=-lmd + else + MD5=no + fi + echo "$MD5" + rm -f _md5.c _md5 +fi +if [ "$MD5" = no ]; then + echo "#include \"compat/crypt/md5.h\"" >>$CONFIG_H + echo "MD5_SRC= compat/crypt/md5.c" >>$CONFIG_MK +else + echo "MD5_SRC=" >>$CONFIG_MK + echo "#define HAVE_MD5_H" >>$CONFIG_H + [ -n "$MD5_LIB" ] && echo "LDADD+= $MD5_LIB" >>$CONFIG_MK +fi + +if [ -z "$SHA2_H" ]; then + if [ -z "$SHA2" ] || [ "$SHA2" != no ]; then + printf "Testing for sha2.h ... " + if [ -e /usr/include/sha2.h ]; then + SHA2_H=sha2.h + elif [ -e /usr/include/sha256.h ]; then + SHA2_H=sha256.h + fi + if [ -n "$SHA2_H" ]; then + echo "$SHA2_H" + else + echo "no" + fi + fi +fi + +if [ -z "$SHA2" ]; then + SHA2_LIB= + SHA2_RENAMED= + printf "Testing for SHA256_Init ... " + cat <<EOF >_sha256.c +#include <sys/types.h> +EOF + [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c + cat <<EOF >>_sha256.c +#include <stdlib.h> +int main(void) { + SHA256_CTX context; + SHA256_Init(&context); + return 0; +} +EOF + # We only want to link to libmd if it exists in /lib + if $ALLOW_USR_LIBS; then + set -- / + else + set -- $(ls /lib/libmd.so.* 2>/dev/null) + fi + if $XCC _sha256.c -o _sha256 2>&3; then + SHA2=yes + elif [ -e "$1" ] && $XCC _sha256.c -lmd -o _sha256 2>&3; then + SHA2="yes (-lmd)" + SHA2_LIB=-lmd + else + SHA2=no + fi + echo "$SHA2" + rm -f _sha256.c _sha256 + if [ "$SHA2" = no ]; then + # Did OpenBSD really change this? grrrr + printf "Testing for SHA256Init ... " + cat <<EOF >_sha256.c +#include <sys/types.h> +EOF + [ -n "$SHA2_H" ] && echo "#include <$SHA2_H>">>_sha256.c + cat <<EOF >>_sha256.c +#include <stdlib.h> +int main(void) { + SHA2_CTX context; + SHA256Init(&context); + return 0; +} +EOF + # We only want to link to libmd if it exists in /lib + if $ALLOW_USR_LIBS; then + set -- / + else + set -- $(ls /lib/libmd.so.* 2>/dev/null) + fi + if $XCC _sha256.c -o _sha256 2>&3; then + SHA2=yes + SHA2_RENAMED=yes + elif [ -e "$1" ] && $XCC _sha256.c -lmd -o _sha256 2>&3 + then + SHA2="yes (-lmd)" + SHA2_LIB=-lmd + SHA2_RENAMED=yes + else + SHA2=no + fi + echo "$SHA2" + rm -f _sha256.c _sha256 + fi +fi +if [ "$SHA2" = no ]; then + echo "#include \"compat/crypt/sha256.h\"" >>$CONFIG_H + echo "SHA256_SRC= compat/crypt/sha256.c" >>$CONFIG_MK +else + echo "SHA256_SRC=" >>$CONFIG_MK + echo "#define SHA2_H <$SHA2_H>" >>$CONFIG_H + if [ "$SHA2_RENAMED" = yes ]; then + echo "#define SHA256_CTX SHA2_CTX" >>$CONFIG_H + echo "#define SHA256_Init SHA256Init" >>$CONFIG_H + echo "#define SHA256_Update SHA256Update" >>$CONFIG_H + echo "#define SHA256_Final SHA256Final" >>$CONFIG_H + fi + [ -n "$SHA2_LIB" ] && echo "LDADD+= $SHA2_LIB" >>$CONFIG_MK +fi + +# Workarond for DragonFlyBSD import +[ "$OS" = dragonfly ] && echo "#endif" >>$CONFIG_H + +if [ -z "$HMAC" ]; then + HMAC_LIB= + printf "Testing for hmac ... " + cat <<EOF >_hmac.c +#include <stdlib.h> +#include <hmac.h> +int main(void) { + hmac(NULL, NULL, 0, NULL, 0, NULL, 0); + return 0; +} +EOF + if $XCC _hmac.c $MD5_LIB -o _hmac 2>&3; then + HMAC=yes + echo "#define HAVE_HMAC_H" >>$CONFIG_H + else + # Remove this test if NetBSD-8 ships with + # hmac in it's own header and not stdlib.h + cat <<EOF >_hmac.c +#include <stdlib.h> +int main(void) { + hmac(NULL, NULL, 0, NULL, 0, NULL, 0); + return 0; +} +EOF + if $XCC _hmac.c $MD5_LIB -o _hmac 2>&3; then + HMAC=yes + else + HMAC=no + fi + fi + echo "$HMAC" + rm -f _hmac.c _hmac +fi +if [ "$HMAC" = no ]; then + echo "#include \"compat/crypt/hmac.h\"" >>$CONFIG_H + echo "HMAC_SRC= compat/crypt/hmac.c" >>$CONFIG_MK +else + # echo "#define HAVE_HMAC_H" >>$CONFIG_H + echo "HMAC_SRC=" >>$CONFIG_MK +fi + +if [ -z "$AUTH" ] || [ "$AUTH" = yes ]; then + if [ "$HMAC" = no ]; then + echo "CRYPT_SRCS+= \${HMAC_SRC}" >>$CONFIG_MK + fi +fi +if [ -z "$INET6" ] || [ "$INET6" = yes ] || \ + [ -z "$AUTH" ] || [ "$AUTH" = yes ]; then + if [ "$MD5" = no ]; then + echo "CRYPT_SRCS+= \${MD5_SRC}" >>$CONFIG_MK + fi + if [ "$SHA2" = no ]; then + echo "CRYPT_SRCS+= \${SHA256_SRC}" >>$CONFIG_MK + fi +fi + +if [ "$DEV" != no ] && [ "$UDEV" != no ]; then + printf "Checking for libudev ... " + if type "$PKG_CONFIG" >/dev/null 2>&1; then + LIBUDEV_CFLAGS=$("$PKG_CONFIG" --cflags libudev 2>&3) + LIBUDEV_LIBS=$("$PKG_CONFIG" --libs libudev 2>&3) + fi + if [ -n "$LIBUDEV_LIBS" ] && [ "$UDEV" = yes ]; then + echo "yes" + elif [ -n "$LIBUDEV_LIBS" ]; then + case "$OS" in + linux*) echo "yes";; + *) echo "yes (disabled)" + # FreeBSD libudev fails to return a udev device + # with udev_device_new_from_subsystem_sysname + # which breaks our test for device initialisation + LIBUDEV_CFLAGS= + LIBUDEV_LIBS= + ;; + esac + else + echo "no" + fi +fi + +if [ "$DEV" != no ] && [ "$UDEV" != no ] && [ -n "$LIBUDEV_LIBS" ]; then + [ -z "$DEV" ] && DEV=yes + echo "DEV_PLUGINS+= udev" >>$CONFIG_MK + if [ -n "$LIBUDEV_CFLAGS" ]; then + echo "LIBUDEV_CFLAGS= $LIBUDEV_CFLAGS" >>$CONFIG_MK + fi + echo "LIBUDEV_LIBS= $LIBUDEV_LIBS" >>$CONFIG_MK + + printf "Checking udev_monitor_filter_add_match_subsystem_devtype ... " + cat <<EOF >_udev.c +#include <libudev.h> +#include <stdlib.h> +int main(void) { + udev_monitor_filter_add_match_subsystem_devtype(NULL, NULL, NULL); + return 0; +} +EOF + if $XCC $LIBUDEV_CFLAGS _udev.c -o _udev $LIBUDEV_LIBS 2>&3 + then + echo "yes" + else + echo "LIBUDEV_CPPFLAGS+= -DLIBUDEV_NOFILTER" >>$CONFIG_MK + echo "no" + fi + rm -f _udev.c _udev + + printf "Checking udev_device_get_is_initialized ... " + cat <<EOF >_udev.c +#include <libudev.h> +#include <stdlib.h> +int main(void) { + udev_device_get_is_initialized(NULL); + return 0; +} +EOF + if $XCC $LIBUDEV_CFLAGS _udev.c -o _udev $LIBUDEV_LIBS 2>&3 + then + echo "yes" + else + echo "LIBUDEV_CPPFLAGS+= -DLIBUDEV_NOINIT" >>$CONFIG_MK + echo "no" + fi + rm -f _udev.c _udev +elif [ "$DEV" != no ] && [ "$UDEV" != no ] && [ -n "$UDEV" ]; then + echo "udev has been explicitly requested ... aborting" >&2 + exit 1 +fi + +if [ "$DEV" = yes ]; then + echo "DHCPCD_SRCS+= dev.c" >>$CONFIG_MK + echo "CPPFLAGS+= -DPLUGIN_DEV" >>$CONFIG_MK + echo "MKDIRS+= dev" >>$CONFIG_MK + + # So the plugins have access to logerr + echo "LDFLAGS+= -Wl,-export-dynamic" >>$CONFIG_MK + + printf "Testing for dlopen ... " + cat <<EOF >_dlopen.c +#include <dlfcn.h> +#include <stdlib.h> +int main(void) { + void *h = dlopen("/foo/bar", 0); + void (*s)(char *) = dlsym(h, "deadbeef"); + s(dlerror()); + dlclose(h); + return 0; +} +EOF + if $XCC _dlopen.c -o _dlopen 2>&3; then + echo "yes" + elif $XCC _dlopen.c -ldl -o _dlopen 2>&3; then + echo "yes (-ldl)" + echo "LDADD+= -ldl" >>$CONFIG_MK + else + echo "no" + echo "libc for dlopen is required - aborting" + fi + rm -f _dlopen.c _dlopen + $abort && exit 1 +fi + +# Transform for a make file +SERVICEEXISTS=$(echo "$SERVICEEXISTS" | $SED \ + -e 's:\\:\\\\:g' \ + -e 's:\&:\\\&:g' \ + -e 's:\$:\\\\\$\$:g' \ +) +echo "SERVICEEXISTS= $SERVICEEXISTS" >>config.mk +SERVICECMD=$(echo "$SERVICECMD" | $SED \ + -e 's:\\:\\\\:g' \ + -e 's:\&:\\\&:g' \ + -e 's:\$:\\\\\$\$:g' \ +) +echo "SERVICECMD= $SERVICECMD" >>config.mk +SERVICESTATUS=$(echo "$SERVICESTATUS" | $SED \ + -e 's:\\:\\\\:g' \ + -e 's:\&:\\\&:g' \ + -e 's:\$:\\\\\$\$:g' \ +) +echo "SERVICESTATUS= $SERVICESTATUS" >>config.mk +if [ -z "$STATUSARG" ]; then + case "$OS" in + dragonfly*|freebsd*) STATUSARG="onestatus";; + esac +fi +echo "STATUSARG= $STATUSARG" >>config.mk + +HOOKS= +if ! $HOOKSET; then + printf "Checking for ntpd ... " + NTPD=$(_which ntpd) + if [ -n "$NTPD" ]; then + echo "$NTPD (50-ntp.conf)" + else + echo "not found" + fi + printf "Checking for chronyd ... " + CHRONYD=$(_which chronyd) + if [ -n "$CHRONYD" ]; then + echo "$CHRONYD (50-ntp.conf)" + else + echo "not found" + fi + if [ -n "$NTPD" ] || [ -n "$CHRONYD" ]; then + HOOKS="$HOOKS${HOOKS:+ }50-ntp.conf" + fi + # Warn if both are detected + if [ -n "$NTPD" ] && [ -n "$CHRONYD" ]; then + echo "NTP will default to $NTPD" + fi + + printf "Checking for ypbind ... " + YPBIND=$(_which ypbind) + if [ -n "$YPBIND" ]; then + YPHOOK="50-ypbind" + if strings "$YPBIND" | $GREP -q yp\\.conf; then + YPHOOK="50-yp.conf" + YPOS="Linux" + elif strings "$YPBIND" | $GREP -q \\.ypservers; then + YPOS="NetBSD" + echo "YPDOMAIN_DIR= /var/yp" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=.ypservers" >>$CONFIG_MK + elif strings "$YPBIND" | $GREP -q /etc/yp; then + YPOS="OpenBSD" + echo "YPDOMAIN_DIR= /etc/yp" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK + else + YPOS="FreeBSD" + echo "YPDOMAIN_DIR=" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK + fi + echo "$YPBIND ($YPHOOK${YPOS:+ }$YPOS)" + EGHOOKS="$EGHOOKS${EGHOOKS:+ }$YPHOOK" + else + echo "not found" + YPHOOK="50-ypbind" + case "$OS" in + linux*) + YPHOOK="50-yp.conf" + YPOS="Linux" + ;; + freebsd*|dragonfly*) + YPOS="FreeBSD" + echo "YPDOMAIN_DIR=" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK + ;; + netbsd*) + YPOS="NetBSD" + echo "YPDOMAIN_DIR= /var/yp" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=.ypservers" >>$CONFIG_MK + ;; + openbsd*) + YPOS="OpenBSD" + echo "YPDOMAIN_DIR= /etc/yp" >>$CONFIG_MK + echo "YPDOMAIN_SUFFIX=" >>$CONFIG_MK + ;; + *) + YPHOOK= + ;; + esac + if [ -n "$YPHOOK" ]; then + echo "Assuming ypbind is $YPOS" + EGHOOKS="$EGHOOKS${EGHOOKS:+ }$YPHOOK" + fi + fi +fi + +find_hook() +{ + for h in [0-9][0-9]"-$x" [0-9][0-9]"-$x.in" \ + [0-9][0-9]"-$x.sh" [0-9][0-9]"-$x.sh.in" \ + [0-9][0-9]"-$x.conf" [0-9][0-9]"-$x.conf.in" + do + [ -e "$h" ] && break + done + [ -e "$h" ] || return 1 + echo "${h%%.in}" + return 0 +} + +if cd hooks; then + for x in $HOOKSCRIPTS; do + printf "Finding hook $x ... " + h=$(find_hook "$x") + if [ -z "$h" ]; then + echo "no" + else + echo "$h" + case " $HOOKS " in + *" $h "*) ;; + *) HOOKS="$HOOKS${HOOKS:+ }$h";; + esac + fi + done + for x in $EGHOOKSCRIPTS; do + printf "Finding example hook $x ... " + h=$(find_hook "$x") + if [ -z "$h" ]; then + echo "no" + else + echo "$h" + case " $EGHOOKS " in + *" $h "*) ;; + *) EGHOOKS="$EGHOOKS${EGHOOKS:+ }$h";; + esac + fi + done + cd .. +fi +echo "HOOKSCRIPTS= $HOOKS" >>$CONFIG_MK +echo "EGHOOKSCRIPTS= $EGHOOKS" >>$CONFIG_MK + +echo +echo " SYSCONFDIR = $SYSCONFDIR" +echo " SBINDIR = $SBINDIR" +echo " LIBDIR = $LIBDIR" +echo " LIBEXECDIR = $LIBEXECDIR" +echo " DBDIR = $DBDIR" +echo " RUNDIR = $RUNDIR" +echo " MANDIR = $MANDIR" +echo " DATADIR = $DATADIR" +echo " HOOKSCRIPTS = $HOOKS" +echo " EGHOOKSCRIPTS = $EGHOOKS" +echo " STATUSARG = $STATUSARG" +if [ "$PRIVSEP" = yes ]; then + echo " PRIVSEPUSER = $PRIVSEP_USER" +fi +echo + +rm -f dhcpcd tests/test diff --git a/hooks/01-test b/hooks/01-test new file mode 100644 index 000000000000..99499425e00d --- /dev/null +++ b/hooks/01-test @@ -0,0 +1,37 @@ +# Echo the interface flags, reason and message options + +if [ "$reason" = "TEST" ]; then + # General variables at the top + set | while read line; do + case "$line" in + interface=*|pid=*|reason=*|protocol=*|profile=*|skip_hooks=*) + echo "$line";; + esac + done + # Interface flags + set | while read line; do + case "$line" in + ifcarrier=*|ifflags=*|ifmetric=*|ifmtu=*|ifwireless=*|ifssid=*) + echo "$line";; + esac + done + # Old lease + set | while read line; do + case "$line" in + old_*) echo "$line";; + esac + done + # New lease + set | while read line; do + case "$line" in + new_*) echo "$line";; + esac + done + # Router Advertisements + set | while read line; do + case "$line" in + nd[0-9]*_*) echo "$line";; + esac + done + exit 0 +fi diff --git a/hooks/10-wpa_supplicant b/hooks/10-wpa_supplicant new file mode 100644 index 000000000000..04acce088840 --- /dev/null +++ b/hooks/10-wpa_supplicant @@ -0,0 +1,113 @@ +# Start, reconfigure and stop wpa_supplicant per wireless interface. +# +# This is only needed when using wpa_supplicant-2.5 or older, OR +# when wpa_supplicant has not been built with CONFIG_MATCH_IFACE, OR +# wpa_supplicant was launched without the -M flag to activate +# interface matching. + +if [ -z "$wpa_supplicant_conf" ]; then + for x in \ + /etc/wpa_supplicant/wpa_supplicant-"$interface".conf \ + /etc/wpa_supplicant/wpa_supplicant.conf \ + /etc/wpa_supplicant-"$interface".conf \ + /etc/wpa_supplicant.conf \ + ; do + if [ -s "$x" ]; then + wpa_supplicant_conf="$x" + break + fi + done +fi +: ${wpa_supplicant_conf:=/etc/wpa_supplicant.conf} + +wpa_supplicant_ctrldir() +{ + dir=$(key_get_value "[[:space:]]*ctrl_interface=" \ + "$wpa_supplicant_conf") + dir=$(trim "$dir") + case "$dir" in + DIR=*) + dir=${dir##DIR=} + dir=${dir%%[[:space:]]GROUP=*} + dir=$(trim "$dir") + ;; + esac + printf %s "$dir" +} + +wpa_supplicant_start() +{ + # If the carrier is up, don't bother checking anything + [ "$ifcarrier" = "up" ] && return 0 + + # Pre flight checks + if [ ! -s "$wpa_supplicant_conf" ]; then + syslog warn \ + "$wpa_supplicant_conf does not exist" + syslog warn "not interacting with wpa_supplicant(8)" + return 1 + fi + dir=$(wpa_supplicant_ctrldir) + if [ -z "$dir" ]; then + syslog warn \ + "ctrl_interface not defined in $wpa_supplicant_conf" + syslog warn "not interacting with wpa_supplicant(8)" + return 1 + fi + + wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1 && return 0 + syslog info "starting wpa_supplicant" + driver=${wpa_supplicant_driver:+-D}$wpa_supplicant_driver + err=$(wpa_supplicant -B -c"$wpa_supplicant_conf" -i"$interface" \ + "$driver" 2>&1) + errn=$? + if [ $errn != 0 ]; then + syslog err "failed to start wpa_supplicant" + syslog err "$err" + fi + return $errn +} + +wpa_supplicant_reconfigure() +{ + dir=$(wpa_supplicant_ctrldir) + [ -z "$dir" ] && return 1 + if ! wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1; then + wpa_supplicant_start + return $? + fi + syslog info "reconfiguring wpa_supplicant" + err=$(wpa_cli -p "$dir" -i "$interface" reconfigure 2>&1) + errn=$? + if [ $errn != 0 ]; then + syslog err "failed to reconfigure wpa_supplicant" + syslog err "$err" + fi + return $errn +} + +wpa_supplicant_stop() +{ + dir=$(wpa_supplicant_ctrldir) + [ -z "$dir" ] && return 1 + wpa_cli -p "$dir" -i "$interface" status >/dev/null 2>&1 || return 0 + syslog info "stopping wpa_supplicant" + err=$(wpa_cli -i"$interface" terminate 2>&1) + errn=$? + if [ $errn != 0 ]; then + syslog err "failed to stop wpa_supplicant" + syslog err "$err" + fi + return $errn +} + +if [ "$ifwireless" = "1" ] && \ + type wpa_supplicant >/dev/null 2>&1 && \ + type wpa_cli >/dev/null 2>&1 +then + case "$reason" in + PREINIT) wpa_supplicant_start;; + RECONFIGURE) wpa_supplicant_reconfigure;; + DEPARTED) wpa_supplicant_stop;; + esac +fi diff --git a/hooks/15-timezone b/hooks/15-timezone new file mode 100644 index 000000000000..3d5173286335 --- /dev/null +++ b/hooks/15-timezone @@ -0,0 +1,47 @@ +# Configure timezone + +: ${localtime:=/etc/localtime} + +set_zoneinfo() +{ + [ -z "$new_tzdb_timezone" ] && return 0 + + zoneinfo_dir= + for d in \ + /usr/share/zoneinfo \ + /usr/lib/zoneinfo \ + /var/share/zoneinfo \ + /var/zoneinfo \ + ; do + if [ -d "$d" ]; then + zoneinfo_dir="$d" + break + fi + done + + if [ -z "$zoneinfo_dir" ]; then + syslog warning "timezone directory not found" + return 1 + fi + + zone_file="$zoneinfo_dir/$new_tzdb_timezone" + if [ ! -e "$zone_file" ]; then + syslog warning "no timezone definition for $new_tzdb_timezone" + return 1 + fi + + if copy_file "$zone_file" "$localtime"; then + syslog info "timezone changed to $new_tzdb_timezone" + fi +} + +# For ease of use, map DHCP6 names onto our DHCP4 names +case "$reason" in +BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) + new_tzdb_timezone="$new_dhcp6_tzdb_timezone" + ;; +esac + +if $if_configured && $if_up; then + set_zoneinfo +fi diff --git a/hooks/20-resolv.conf b/hooks/20-resolv.conf new file mode 100644 index 000000000000..504a6c5373a4 --- /dev/null +++ b/hooks/20-resolv.conf @@ -0,0 +1,224 @@ +# Generate /etc/resolv.conf +# Support resolvconf(8) if available +# We can merge other dhcpcd resolv.conf files into one like resolvconf, +# but resolvconf is preferred as other applications like VPN clients +# can readily hook into it. +# Also, resolvconf can configure local nameservers such as bind +# or dnsmasq. This is important as the libc resolver isn't that powerful. + +resolv_conf_dir="$state_dir/resolv.conf" +nocarrier_roaming_dir="$state_dir/roaming" +NL=" +" +: ${resolvconf:=resolvconf} +if type "$resolvconf" >/dev/null 2>&1; then + have_resolvconf=true +else + have_resolvconf=false +fi + +build_resolv_conf() +{ + cf="$state_dir/resolv.conf.$ifname" + + # Build a list of interfaces + interfaces=$(list_interfaces "$resolv_conf_dir") + + # Build the resolv.conf + header= + if [ -n "$interfaces" ]; then + # Build the header + for x in ${interfaces}; do + header="$header${header:+, }$x" + done + + # Build the search list + domain=$(cd "$resolv_conf_dir"; \ + key_get_value "domain " ${interfaces}) + search=$(cd "$resolv_conf_dir"; \ + key_get_value "search " ${interfaces}) + set -- ${domain} + domain="$1" + [ -n "$2" ] && search="$search $*" + [ -n "$search" ] && search="$(uniqify $search)" + [ "$domain" = "$search" ] && search= + [ -n "$domain" ] && domain="domain $domain$NL" + [ -n "$search" ] && search="search $search$NL" + + # Build the nameserver list + srvs=$(cd "$resolv_conf_dir"; \ + key_get_value "nameserver " ${interfaces}) + for x in $(uniqify $srvs); do + servers="${servers}nameserver $x$NL" + done + fi + header="$signature_base${header:+ $from }$header" + + # Assemble resolv.conf using our head and tail files + [ -f "$cf" ] && rm -f "$cf" + [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir" + echo "$header" > "$cf" + if [ -f /etc/resolv.conf.head ]; then + cat /etc/resolv.conf.head >> "$cf" + else + echo "# /etc/resolv.conf.head can replace this line" >> "$cf" + fi + printf %s "$domain$search$servers" >> "$cf" + if [ -f /etc/resolv.conf.tail ]; then + cat /etc/resolv.conf.tail >> "$cf" + else + echo "# /etc/resolv.conf.tail can replace this line" >> "$cf" + fi + if change_file /etc/resolv.conf "$cf"; then + chmod 644 /etc/resolv.conf + fi + rm -f "$cf" +} + +# Extract any ND DNS options from the RA +# Obey the lifetimes +eval_nd_dns() +{ + + eval rdnsstime=\$nd${i}_rdnss${j}_lifetime + [ -z "$rdnsstime" ] && return 1 + ltime=$(($rdnsstime - $offset)) + if [ "$ltime" -gt 0 ]; then + eval rdnss=\$nd${i}_rdnss${j}_servers + [ -n "$rdnss" ] && new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss" + fi + + eval dnssltime=\$nd${i}_dnssl${j}_lifetime + [ -z "$dnssltime" ] && return 1 + ltime=$(($dnssltime - $offset)) + if [ "$ltime" -gt 0 ]; then + eval dnssl=\$nd${i}_dnssl${j}_search + [ -n "$dnssl" ] && new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl" + fi + + j=$(($j + 1)) + return 0 +} + +add_resolv_conf() +{ + conf="$signature$NL" + warn=true + + # Loop to extract the ND DNS options using our indexed shell values + i=1 + j=1 + while true; do + eval acquired=\$nd${i}_acquired + [ -z "$acquired" ] && break + eval now=\$nd${i}_now + [ -z "$now" ] && break + offset=$(($now - $acquired)) + while true; do + eval_nd_dns || break + done + i=$(($i + 1)) + j=1 + done + [ -n "$new_rdnss" ] && \ + new_domain_name_servers="$new_domain_name_servers${new_domain_name_servers:+ }$new_rdnss" + [ -n "$new_dnssl" ] && \ + new_domain_search="$new_domain_search${new_domain_search:+ }$new_dnssl" + + # Derive a new domain from our various hostname options + if [ -z "$new_domain_name" ]; then + if [ "$new_dhcp6_fqdn" != "${new_dhcp6_fqdn#*.}" ]; then + new_domain_name="${new_dhcp6_fqdn#*.}" + elif [ "$new_fqdn" != "${new_fqdn#*.}" ]; then + new_domain_name="${new_fqdn#*.}" + elif [ "$new_host_name" != "${new_host_name#*.}" ]; then + new_domain_name="${new_host_name#*.}" + fi + fi + + # If we don't have any configuration, remove it + if [ -z "$new_domain_name_servers" ] && + [ -z "$new_domain_name" ] && + [ -z "$new_domain_search" ]; then + remove_resolv_conf + return $? + fi + + if [ -n "$new_domain_name" ]; then + set -- $new_domain_name + if valid_domainname "$1"; then + conf="${conf}domain $1$NL" + else + syslog err "Invalid domain name: $1" + fi + # If there is no search this, make this one + if [ -z "$new_domain_search" ]; then + new_domain_search="$new_domain_name" + [ "$new_domain_name" = "$1" ] && warn=true + fi + fi + if [ -n "$new_domain_search" ]; then + new_domain_search=$(uniqify $new_domain_search) + if valid_domainname_list $new_domain_search; then + conf="${conf}search $new_domain_search$NL" + elif ! $warn; then + syslog err "Invalid domain name in list:" \ + "$new_domain_search" + fi + fi + new_domain_name_servers=$(uniqify $new_domain_name_servers) + for x in ${new_domain_name_servers}; do + conf="${conf}nameserver $x$NL" + done + if $have_resolvconf; then + [ -n "$ifmetric" ] && export IF_METRIC="$ifmetric" + printf %s "$conf" | "$resolvconf" -a "$ifname" + return $? + fi + + if [ -e "$resolv_conf_dir/$ifname" ]; then + rm -f "$resolv_conf_dir/$ifname" + fi + [ -d "$resolv_conf_dir" ] || mkdir -p "$resolv_conf_dir" + printf %s "$conf" > "$resolv_conf_dir/$ifname" + build_resolv_conf +} + +remove_resolv_conf() +{ + if $have_resolvconf; then + "$resolvconf" -d "$ifname" -f + else + if [ -e "$resolv_conf_dir/$ifname" ]; then + rm -f "$resolv_conf_dir/$ifname" + fi + build_resolv_conf + fi +} + +# For ease of use, map DHCP6 names onto our DHCP4 names +case "$reason" in +BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) + new_domain_name_servers="$new_dhcp6_name_servers" + new_domain_search="$new_dhcp6_domain_search" + ;; +esac + +if $if_configured; then + if $have_resolvconf && [ "$reason" = NOCARRIER_ROAMING ]; then + # avoid calling resolvconf -c on CARRIER unless we roam + mkdir -p "$nocarrier_roaming_dir" + echo " " >"$nocarrier_roaming_dir/$interface" + "$resolvconf" -C "$interface.*" + elif $have_resolvconf && [ "$reason" = CARRIER ]; then + # Not all resolvconf implementations support -c + if [ -e "$nocarrier_roaming_dir/$interface" ]; then + rm -f "$nocarrier_roaming_dir/$interface" + "$resolvconf" -c "$interface.*" + fi + elif $if_up || [ "$reason" = ROUTERADVERT ]; then + add_resolv_conf + elif $if_down; then + remove_resolv_conf + fi +fi diff --git a/hooks/29-lookup-hostname b/hooks/29-lookup-hostname new file mode 100644 index 000000000000..811c7a9c89bf --- /dev/null +++ b/hooks/29-lookup-hostname @@ -0,0 +1,39 @@ +# Lookup the hostname in DNS if not set + +lookup_hostname() +{ + [ -z "$new_ip_address" ] && return 1 + # Silly ISC programs love to send error text to stdout + if type dig >/dev/null 2>&1; then + h=$(dig +short -x $new_ip_address) + if [ $? = 0 ]; then + echo "$h" | sed 's/\.$//' + return 0 + fi + elif type host >/dev/null 2>&1; then + h=$(host $new_ip_address) + if [ $? = 0 ]; then + echo "$h" \ + | sed 's/.* domain name pointer \(.*\)./\1/' + return 0 + fi + elif type getent >/dev/null 2>&1; then + h=$(getent hosts $new_ip_address) + if [ $? = 0 ]; then + echo "$h" | sed 's/[^ ]* *\([^ ]*\).*/\1/' + return 0 + fi + fi + return 1 +} + +set_hostname() +{ + if [ -z "${new_host_name}${new_fqdn_name}" ]; then + export new_host_name="$(lookup_hostname)" + fi +} + +if $if_up; then + set_hostname +fi diff --git a/hooks/30-hostname.in b/hooks/30-hostname.in new file mode 100644 index 000000000000..abeb36967221 --- /dev/null +++ b/hooks/30-hostname.in @@ -0,0 +1,158 @@ +# Set the hostname from DHCP data if required + +# A hostname can either be a short hostname or a FQDN. +# hostname_fqdn=true +# hostname_fqdn=false +# hostname_fqdn=server + +# A value of server means just what the server says, don't manipulate it. +# This could lead to an inconsistent hostname on a DHCPv4 and DHCPv6 network +# where the DHCPv4 hostname is short and the DHCPv6 has an FQDN. +# DHCPv6 has no hostname option. +# RFC4702 section 3.1 says FQDN should be prefered over hostname. +# +# As such, the default is hostname_fqdn=true so that a consistent hostname +# is always assigned. +: ${hostname_fqdn:=true} + +# If we used to set the hostname, but relinquish control of it, we should +# reset to the default value. +: ${hostname_default=@DEFAULT_HOSTNAME@} + +# Some systems don't have hostname(1) +_hostname() +{ + if [ -z "${1+x}" ]; then + if [ -r /proc/sys/kernel/hostname ]; then + read name </proc/sys/kernel/hostname && echo "$name" + elif type hostname >/dev/null 2>/dev/null; then + hostname + elif sysctl kern.hostname >/dev/null 2>&1; then + sysctl -n kern.hostname + elif sysctl kernel.hostname >/dev/null 2>&1; then + sysctl -n kernel.hostname + else + return 1 + fi + return $? + fi + + if [ -w /proc/sys/kernel/hostname ]; then + echo "$1" >/proc/sys/kernel/hostname + elif [ -n "$1" ] && type hostname >/dev/null 2>&1; then + hostname "$1" + elif sysctl kern.hostname >/dev/null 2>&1; then + sysctl -w "kern.hostname=$1" >/dev/null + elif sysctl kernel.hostname >/dev/null 2>&1; then + sysctl -w "kernel.hostname=$1" >/dev/null + else + # May fail to set a blank hostname + hostname "$1" + fi +} + +is_default_hostname() +{ + case "$1" in + ""|"$hostname_default"|localhost|localhost.localdomain) + return 0;; + esac + return 1 +} + +need_hostname() +{ + # Always load the hostname variable for future use + hostname="$(_hostname)" + is_default_hostname "$hostname" && return 0 + + case "$force_hostname" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) return 0;; + esac + + if [ -n "$old_fqdn" ]; then + if ${hfqdn} || ! ${hshort}; then + [ "$hostname" = "$old_fqdn" ] + else + [ "$hostname" = "${old_fqdn%%.*}" ] + fi + elif [ -n "$old_host_name" ]; then + if ${hfqdn}; then + if [ -n "$old_domain_name" ] && + [ "$old_host_name" = "${old_host_name#*.}" ] + then + [ "$hostname" = \ + "$old_host_name.$old_domain_name" ] + else + [ "$hostname" = "$old_host_name" ] + fi + elif ${hshort}; then + [ "$hostname" = "${old_host_name%%.*}" ] + else + [ "$hostname" = "$old_host_name" ] + fi + else + # No old hostname + false + fi +} + +try_hostname() +{ + [ "$hostname" = "$1" ] && return 0 + if valid_domainname "$1"; then + syslog info "Setting hostname: $1" + _hostname "$1" + else + syslog err "Invalid hostname: $1" + fi +} + +set_hostname() +{ + hfqdn=false + hshort=false + case "$hostname_fqdn" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;; + ""|[Ss][Ee][Rr][Vv][Ee][Rr]) ;; + *) hshort=true;; + esac + + need_hostname || return + + if [ -n "$new_fqdn" ]; then + if ${hfqdn} || ! ${hshort}; then + try_hostname "$new_fqdn" + else + try_hostname "${new_fqdn%%.*}" + fi + elif [ -n "$new_host_name" ]; then + if ${hfqdn}; then + if [ -n "$new_domain_name" ] && + [ "$new_host_name" = "${new_host_name#*.}" ] + then + try_hostname "$new_host_name.$new_domain_name" + else + try_hostname "$new_host_name" + fi + elif ${hshort}; then + try_hostname "${new_host_name%%.*}" + else + try_hostname "$new_host_name" + fi + elif ! is_default_hostname "$hostname"; then + try_hostname "$hostname_default" + fi +} + +# For ease of use, map DHCP6 names onto our DHCP4 names +case "$reason" in +BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) + new_fqdn="$new_dhcp6_fqdn" + old_fqdn="$old_dhcp6_fqdn" + ;; +esac + +if $if_configured && $if_up && [ "$reason" != ROUTERADVERT ]; then + set_hostname +fi diff --git a/hooks/50-dhcpcd-compat b/hooks/50-dhcpcd-compat new file mode 100644 index 000000000000..0d6256e6ae6c --- /dev/null +++ b/hooks/50-dhcpcd-compat @@ -0,0 +1,41 @@ +# Compat enter hook shim for older dhcpcd versions + +IPADDR=$new_ip_address +INTERFACE=$interface +NETMASK=$new_subnet_mask +BROADCAST=$new_broadcast_address +NETWORK=$new_network_number +DHCPSID=$new_dhcp_server_identifier +GATEWAYS=$new_routers +DNSSERVERS=$new_domain_name_servers +DNSDOMAIN=$new_domain_name +DNSSEARCH=$new_domain_search +NISDOMAIN=$new_nis_domain +NISSERVERS=$new_nis_servers +NTPSERVERS=$new_ntp_servers + +GATEWAY= +for x in $new_routers; do + GATEWAY="$GATEWAY${GATEWAY:+,}$x" +done +DNS= +for x in $new_domain_name_servers; do + DNS="$DNS${DNS:+,}$x" +done + +r="down" +case "$reason" in +RENEW) r="up";; +BOUND|INFORM|REBIND|REBOOT|TEST|TIMEOUT|IPV4LL) r="new";; +esac + +if [ "$r" != "down" ]; then + rm -f /var/lib/dhcpcd-"$INTERFACE".info + for x in IPADDR INTERFACE NETMASK BROADCAST NETWORK DHCPSID GATEWAYS \ + DNSSERVERS DNSDOMAIN DNSSEARCH NISDOMAIN NISSERVERS \ + NTPSERVERS GATEWAY DNS; do + eval echo "$x=\'\$$x\'" >> /var/lib/dhcpcd-"$INTERFACE".info + done +fi + +set -- /var/lib/dhcpcd-"$INTERFACE".info "$r" diff --git a/hooks/50-ntp.conf b/hooks/50-ntp.conf new file mode 100644 index 000000000000..046ab6b33a06 --- /dev/null +++ b/hooks/50-ntp.conf @@ -0,0 +1,144 @@ +# Sample dhcpcd hook script for NTP +# It will configure either one of NTP, OpenNTP or Chrony (in that order) +# and will default to NTP if no default config is found. + +# Like our resolv.conf hook script, we store a database of ntp.conf files +# and merge into /etc/ntp.conf + +# You can set the env var NTP_CONF to override the derived default on +# systems with >1 NTP client installed. +# Here is an example for OpenNTP +# dhcpcd -e NTP_CONF=/usr/pkg/etc/ntpd.conf +# or by adding this to /etc/dhcpcd.conf +# env NTP_CONF=/usr/pkg/etc/ntpd.conf +# or by adding this to /etc/dhcpcd.enter-hook +# NTP_CONF=/usr/pkg/etc/ntpd.conf +# To use Chrony instead, simply change ntpd.conf to chrony.conf in the +# above examples. + +: ${ntp_confs:=ntp.conf ntpd.conf chrony.conf} +: ${ntp_conf_dirs=/etc /usr/pkg/etc /usr/local/etc} +ntp_conf_dir="$state_dir/ntp.conf" + +# If NTP_CONF is not set, work out a good default +if [ -z "$NTP_CONF" ]; then + for d in ${ntp_conf_dirs}; do + for f in ${ntp_confs}; do + if [ -e "$d/$f" ]; then + NTP_CONF="$d/$f" + break 2 + fi + done + done + [ -e "$NTP_CONF" ] || NTP_CONF=/etc/ntp.conf +fi + +# Derive service name from configuration +if [ -z "$ntp_service" ]; then + case "$NTP_CONF" in + *chrony.conf) ntp_service=chronyd;; + *) ntp_service=ntpd;; + esac +fi + +# Debian has a separate file for DHCP config to avoid stamping on +# the master. +if [ "$ntp_service" = ntpd ] && type invoke-rc.d >/dev/null 2>&1; then + [ -e /var/lib/ntp ] || mkdir /var/lib/ntp + : ${ntp_service:=ntp} + : ${NTP_DHCP_CONF:=/var/lib/ntp/ntp.conf.dhcp} +fi + +: ${ntp_restart_cmd:=service_condcommand $ntp_service restart} + +ntp_conf=${NTP_CONF} +NL=" +" + +build_ntp_conf() +{ + cf="$state_dir/ntp.conf.$ifname" + + # Build a list of interfaces + interfaces=$(list_interfaces "$ntp_conf_dir") + + header= + servers= + if [ -n "$interfaces" ]; then + # Build the header + for x in ${interfaces}; do + header="$header${header:+, }$x" + done + + # Build a server list + srvs=$(cd "$ntp_conf_dir"; + key_get_value "server " $interfaces) + if [ -n "$srvs" ]; then + for x in $(uniqify $srvs); do + servers="${servers}server $x$NL" + done + fi + fi + + # Merge our config into ntp.conf + [ -e "$cf" ] && rm -f "$cf" + [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir" + + if [ -n "$NTP_DHCP_CONF" ]; then + [ -e "$ntp_conf" ] && cp "$ntp_conf" "$cf" + ntp_conf="$NTP_DHCP_CONF" + elif [ -e "$ntp_conf" ]; then + remove_markers "$signature_base" "$signature_base_end" \ + "$ntp_conf" > "$cf" + fi + + if [ -n "$servers" ]; then + echo "$signature_base${header:+ $from }$header" >> "$cf" + printf %s "$servers" >> "$cf" + echo "$signature_base_end${header:+ $from }$header" >> "$cf" + else + [ -e "$ntp_conf" ] && [ -e "$cf" ] || return + fi + + # If we changed anything, restart ntpd + if change_file "$ntp_conf" "$cf"; then + [ -n "$ntp_restart_cmd" ] && eval $ntp_restart_cmd + fi +} + +add_ntp_conf() +{ + cf="$ntp_conf_dir/$ifname" + + [ -e "$cf" ] && rm "$cf" + [ -d "$ntp_conf_dir" ] || mkdir -p "$ntp_conf_dir" + if [ -n "$new_ntp_servers" ]; then + for x in $new_ntp_servers; do + echo "server $x" >> "$cf" + done + fi + build_ntp_conf +} + +remove_ntp_conf() +{ + if [ -e "$ntp_conf_dir/$ifname" ]; then + rm "$ntp_conf_dir/$ifname" + fi + build_ntp_conf +} + +# For ease of use, map DHCP6 names onto our DHCP4 names +case "$reason" in +BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) + new_ntp_servers="$new_dhcp6_sntp_servers" +;; +esac + +if $if_configured; then + if $if_up; then + add_ntp_conf + elif $if_down; then + remove_ntp_conf + fi +fi diff --git a/hooks/50-yp.conf b/hooks/50-yp.conf new file mode 100644 index 000000000000..c5cdad90260d --- /dev/null +++ b/hooks/50-yp.conf @@ -0,0 +1,59 @@ +# Sample dhcpcd hook for ypbind +# This script is only suitable for the Linux version. + +ypbind_pid() +{ + [ -s /var/run/ypbind.pid ] && cat /var/run/ypbind.pid +} + +make_yp_conf() +{ + [ -z "${new_nis_domain}${new_nis_servers}" ] && return 0 + cf=/etc/yp.conf."$ifname" + rm -f "$cf" + echo "$signature" > "$cf" + prefix= + if [ -n "$new_nis_domain" ]; then + if ! valid_domainname "$new_nis_domain"; then + syslog err "Invalid NIS domain name: $new_nis_domain" + rm -f "$cf" + return 1 + fi + domainname "$new_nis_domain" + if [ -n "$new_nis_servers" ]; then + prefix="domain $new_nis_domain server " + else + echo "domain $new_nis_domain broadcast" >> "$cf" + fi + else + prefix="ypserver " + fi + for x in $new_nis_servers; do + echo "$prefix$x" >> "$cf" + done + save_conf /etc/yp.conf + cat "$cf" > /etc/yp.conf + rm -f "$cf" + pid="$(ypbind_pid)" + if [ -n "$pid" ]; then + kill -HUP "$pid" + fi +} + +restore_yp_conf() +{ + [ -n "$old_nis_domain" ] && domainname "" + restore_conf /etc/yp.conf || return 0 + pid="$(ypbind_pid)" + if [ -n "$pid" ]; then + kill -HUP "$pid" + fi +} + +if $if_configured; then + if $if_up; then + make_yp_conf + elif $if_down; then + restore_yp_conf + fi +fi diff --git a/hooks/50-ypbind.in b/hooks/50-ypbind.in new file mode 100644 index 000000000000..6d55228c78c6 --- /dev/null +++ b/hooks/50-ypbind.in @@ -0,0 +1,84 @@ +# Sample dhcpcd hook for ypbind +# This script is only suitable for the BSD versions. + +: ${ypbind_restart_cmd:=service_command ypbind restart} +: ${ypbind_stop_cmd:=service_condcommand ypbind stop} +ypbind_dir="$state_dir/ypbind" +: ${ypdomain_dir:=@YPDOMAIN_DIR@} +: ${ypdomain_suffix:=@YPDOMAIN_SUFFIX@} + +best_domain() +{ + for i in "$ypbind_dir/$interface_order".*; do + if [ -f "$i" ]; then + cat "$i" + return 0 + fi + done + return 1 +} + +make_yp_binding() +{ + [ -d "$ypbind_dir" ] || mkdir -p "$ypbind_dir" + echo "$new_nis_domain" >"$ypbind_dir/$ifname" + + if [ -z "$ypdomain_dir" ]; then + false + else + cf="$ypdomain_dir/$new_nis_domain$ypdomain_suffix" + if [ -n "$new_nis_servers" ]; then + ncf="$cf.$ifname" + rm -f "$ncf" + for x in $new_nis_servers; do + echo "$x" >>"$ncf" + done + change_file "$cf" "$ncf" + else + [ -e "$cf" ] && rm "$cf" + fi + fi + + nd="$(best_domain)" + if [ $? = 0 ] && [ "$nd" != "$(domainname)" ]; then + domainname "$nd" + if [ -n "$ypbind_restart_cmd" ]; then + eval $ypbind_restart_cmd + fi + fi +} + +restore_yp_binding() +{ + rm -f "$ypbind_dir/$ifname" + nd="$(best_domain)" + # We need to stop ypbind if there is no best domain + # otherwise it will just stall as we cannot set domainname + # to blank :/ + if [ -z "$nd" ]; then + if [ -n "$ypbind_stop_cmd" ]; then + eval $ypbind_stop_cmd + fi + elif [ "$nd" != "$(domainname)" ]; then + domainname "$nd" + if [ -n "$ypbind_restart_cmd" ]; then + eval $ypbind_restart_cmd + fi + fi +} + +if ! $if_configured; then + ; +elif [ "$reason" = PREINIT ]; then + rm -f "$ypbind_dir/$interface".* +elif $if_up || $if_down; then + if [ -n "$new_nis_domain" ]; then + if valid_domainname "$new_nis_domain"; then + make_yp_binding + else + syslog err "Invalid NIS domain name: $new_nis_domain" + fi + elif [ -n "$old_nis_domain" ]; then + restore_yp_binding + fi +fi diff --git a/hooks/Makefile b/hooks/Makefile new file mode 100644 index 000000000000..7a0c3b1d330a --- /dev/null +++ b/hooks/Makefile @@ -0,0 +1,75 @@ +TOP= ../ +include ${TOP}/iconfig.mk + +PROG= dhcpcd-run-hooks +BINDIR= ${LIBEXECDIR} +CLEANFILES= dhcpcd-run-hooks +MAN8= dhcpcd-run-hooks.8 +CLEANFILES+= dhcpcd-run-hooks.8 + +SCRIPTSDIR= ${HOOKDIR} +SCRIPTS= 01-test +SCRIPTS+= 20-resolv.conf +SCRIPTS+= 30-hostname +SCRIPTS+= ${HOOKSCRIPTS} +CLEANFILES+= 30-hostname + +# Some hooks should not be installed by default +FILESDIR= ${DATADIR}/dhcpcd/hooks +FILES= 10-wpa_supplicant +FILES+= 15-timezone +FILES+= 29-lookup-hostname +FILES+= ${EGHOOKSCRIPTS} + +.SUFFIXES: .in + +.in: Makefile ${TOP}/config.mk + ${SED} ${SED_RUNDIR} ${SED_DBDIR} ${SED_LIBDIR} ${SED_HOOKDIR} \ + ${SED_SYS} ${SED_SCRIPT} ${SED_DATADIR} \ + ${SED_SERVICEEXISTS} ${SED_SERVICECMD} ${SED_SERVICESTATUS} \ + ${SED_STATUSARG} \ + ${SED_DEFAULT_HOSTNAME} \ + -e 's:@YPDOMAIN_DIR@:${YPDOMAIN_DIR}:g' \ + -e 's:@YPDOMAIN_SUFFIX@:${YPDOMAIN_SUFFIX}:g' \ + $< > $@ + +all: ${PROG} ${MAN8} ${SCRIPTS} ${EGHOOKSCRIPTS} + +clean: + rm -f ${CLEANFILES} 50-ypbind + +distclean: clean + rm -f *.diff *.patch *.orig *.rej + +depend: + +proginstall: ${PROG} ${SCRIPTS} + ${INSTALL} -d ${DESTDIR}${BINDIR} + ${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${BINDIR} + ${INSTALL} -d ${DESTDIR}${SCRIPTSDIR} + ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} ${DESTDIR}${SCRIPTSDIR} + # We need to remove the old MTU change script if we at all can. + rm -f ${DESTDIR}${SCRIPTSDIR}/10-mtu + +eginstall: ${FILES} + ${INSTALL} -d ${DESTDIR}${FILESDIR} + ${INSTALL} -m ${NONBINMODE} ${FILES} ${DESTDIR}${FILESDIR} + +maninstall: ${MAN8} + ${INSTALL} -d ${DESTDIR}${MANDIR}/man8 + ${INSTALL} -m ${MANMODE} ${MAN8} ${DESTDIR}${MANDIR}/man8 + +install: proginstall eginstall maninstall + +import: ${SCRIPTS} ${FILES} + ${INSTALL} -d /tmp/${DISTPREFIX}/dhcpcd-hooks + ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} /tmp/${DISTPREFIX}/dhcpcd-hooks + ${INSTALL} -m ${NONBINMODE} ${FILES} /tmp/${DISTPREFIX}/dhcpcd-hooks + +_import-src: all + ${INSTALL} -d ${DESTDIR}/hooks + ${INSTALL} -m ${NONBINMODE} ${PROG} ${MAN8} ${DESTDIR}/hooks + ${INSTALL} -m ${NONBINMODE} ${SCRIPTS} ${DESTDIR}/hooks + ${INSTALL} -m ${NONBINMODE} ${FILES} ${DESTDIR}/hooks + +include ${TOP}/Makefile.inc diff --git a/hooks/dhcpcd-run-hooks.8.in b/hooks/dhcpcd-run-hooks.8.in new file mode 100644 index 000000000000..db88d8e2ab17 --- /dev/null +++ b/hooks/dhcpcd-run-hooks.8.in @@ -0,0 +1,234 @@ +.\" 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. +.\" +.Dd December 27, 2020 +.Dt DHCPCD-RUN-HOOKS 8 +.Os +.Sh NAME +.Nm dhcpcd-run-hooks +.Nd DHCP client configuration script +.Sh DESCRIPTION +.Nm +is used by +.Xr dhcpcd 8 +to run any system and user defined hook scripts. +System hook scripts are found in +.Pa @HOOKDIR@ +and the user defined hooks are +.Pa @SYSCONFDIR@/dhcpcd.enter-hook . +and +.Pa @SYSCONFDIR@/dhcpcd.exit-hook . +The default install supplies hook scripts for configuring +.Pa /etc/resolv.conf +and the hostname. +Your distribution may have included other hook scripts to say configure +ntp or ypbind. +A test hook is also supplied that simply echos the dhcp variables to the +console from DISCOVER message. +.Pp +The hooks scripts are loaded into the current shell rather than executed +in their own process. +This allows each hook script, such as +.Pa @SYSCONFDIR@/dhcpcd.enter-hook +to customise environment variables or provide alternative functions to hooks +further down the chain. +As such, using the shell builtins +.Ic exit , +.Ic exec +or similar will cause +.Nm +to exit at that point. +.Pp +Each time +.Nm +is invoked, +.Ev $interface +is set to the interface that +.Nm dhcpcd +is run on and +.Ev $reason +is to the reason why +q +.Nm +was invoked. +DHCP information to be configured is held in variables starting with the word +new_ and old DHCP information to be removed is held in variables starting with +the word old_. +.Nm dhcpcd +can display the full list of variables it knows how about by using the +.Fl V , -variables +argument. +.Pp +Here's a list of reasons why +.Nm +could be invoked: +.Bl -tag -width EXPIREXXXEXPIRE6 +.It Dv PREINIT +dhcpcd is starting up and any pre-initialisation should be done. +.It Dv CARRIER +dhcpcd has detected the carrier is up. +This is generally just a notification and no action need be taken. +.It Dv NOCARRIER +dhcpcd lost the carrier. +The cable may have been unplugged or association to the wireless point lost. +.It Dv NOCARRIER_ROAMING +dhcpcd lost the carrier but the interface configuration is persisted. +The OS has to support wireless roaming or IP Persistance for this to happen. +.It Dv INFORM | Dv INFORM6 +dhcpcd informed a DHCP server about its address and obtained other +configuration details. +.It Dv BOUND | Dv BOUND6 +dhcpcd obtained a new lease from a DHCP server. +.It Dv RENEW | Dv RENEW6 +dhcpcd renewed it's lease. +.It Dv REBIND | Dv REBIND6 +dhcpcd has rebound to a new DHCP server. +.It Dv REBOOT | Dv REBOOT6 +dhcpcd successfully requested a lease from a DHCP server. +.It Dv DELEGATED6 +dhcpcd assigned a delegated prefix to the interface. +.It Dv IPV4LL +dhcpcd obtained an IPV4LL address, or one was removed. +.It Dv STATIC +dhcpcd has been configured with a static configuration which has not been +obtained from a DHCP server. +.It Dv 3RDPARTY +dhcpcd is monitoring the interface for a 3rd party to give it an IP address. +.It Dv TIMEOUT +dhcpcd failed to contact any DHCP servers but was able to use an old lease. +.It Dv EXPIRE | EXPIRE6 +dhcpcd's lease or state expired and it failed to obtain a new one. +.It Dv NAK +dhcpcd received a NAK from the DHCP server. +This should be treated as EXPIRE. +.It Dv RECONFIGURE +dhcpcd has been instructed to reconfigure an interface. +.It Dv ROUTERADVERT +dhcpcd has received an IPv6 Router Advertisement, or one has expired. +.It Dv STOP | Dv STOP6 +dhcpcd stopped running on the interface. +.It Dv STOPPED +dhcpcd has stopped entirely. +.It Dv DEPARTED +The interface has been removed. +.It Dv FAIL +dhcpcd failed to operate on the interface. +This normally happens when dhcpcd does not support the raw interface, which +means it cannot work as a DHCP or ZeroConf client. +Static configuration and DHCP INFORM is still allowed. +.It Dv TEST +dhcpcd received an OFFER from a DHCP server but will not configure the +interface. +This is primarily used to test the variables are filled correctly for the +script to process them. +.El +.Sh ENVIRONMENT +.Nm dhcpcd +will clear the environment variables aside from +.Ev $PATH . +The following variables will then be set, along with any protocol supplied +ones. +.Bl -tag -width xnew_delegated_dhcp6_prefix +.It Ev $interface +the name of the interface. +.It Ev $protocol +the protocol that triggered the event. +.It Ev $reason +as described above. +.It Ev $pid +the pid of +.Nm dhcpcd . +.It Ev $ifcarrier +the link status of +.Ev $interface : +.Dv unknown , +.Dv up +or +.Dv down . +.It Ev $ifmetric +.Ev $interface +preference, lower is better. +.It Ev $ifwireless +.Dv 1 if +.Ev $interface +is wireless, otherwise +.Dv 0 . +.It Ev $ifflags +.Ev $interface +flags. +.It Ev $ifmtu +.Ev $interface +MTU. +.It Ev $ifssid +the name of the SSID the +.Ev interface +is connected to. +.It Ev $interface_order +A list of interfaces, in order of preference. +.It Ev $if_up +.Dv true +if the +.Ev interface +is up, otherwise +.Dv false . +This is more than IFF_UP and may not be equal. +.It Ev $if_down +.Dv true +if the +.Ev interface +is down, otherwise +.Dv false . +This is more than IFF_UP and may not be equal. +.It Ev $af_waiting +Address family waiting for, as defined in +.Xr dhcpcd.conf 5 . +.It Ev $profile +the name of the profile selected from +.Xr dhcpcd.conf 5 . +.It Ev $new_delegated_dhcp6_prefix +space separated list of delegated prefixes. +.El +.Sh FILES +When +.Nm +runs, it loads +.Pa @SYSCONFDIR@/dhcpcd.enter-hook +and any scripts found in +.Pa @HOOKDIR@ +in a lexical order and then finally +.Pa @SYSCONFDIR@/dhcpcd.exit-hook +.Sh SEE ALSO +.Xr dhcpcd 8 +.Sh AUTHORS +.An Roy Marples Aq Mt roy@marples.name +.Sh BUGS +Please report them to +.Lk http://roy.marples.name/projects/dhcpcd +.Sh SECURITY CONSIDERATIONS +.Nm dhcpcd +will validate the content of each option against its encoding. +For string, ascii, raw or binhex encoding it's up to the user to validate it +for the intended purpose. +.Pp +When used in a shell script, each variable must be quoted correctly. diff --git a/hooks/dhcpcd-run-hooks.in b/hooks/dhcpcd-run-hooks.in new file mode 100644 index 000000000000..a237f6af5340 --- /dev/null +++ b/hooks/dhcpcd-run-hooks.in @@ -0,0 +1,352 @@ +#!/bin/sh +# dhcpcd client configuration script + +# Handy variables and functions for our hooks to use +ifname="$interface${protocol+.}$protocol" +from=from +signature_base="# Generated by dhcpcd" +signature="$signature_base $from $ifname" +signature_base_end="# End of dhcpcd" +signature_end="$signature_base_end $from $ifname" +state_dir=@RUNDIR@/hook-state +_detected_init=false + +: ${if_up:=false} +: ${if_down:=false} +: ${syslog_debug:=false} + +# Ensure that all arguments are unique +uniqify() +{ + result= + for i do + case " $result " in + *" $i "*);; + *) result="$result${result:+ }$i";; + esac + done + echo "$result" +} + +# List interface config files in a directory. +# If dhcpcd is running as a single instance then it will have a list of +# interfaces in the preferred order. +# Otherwise we just use what we have. +list_interfaces() +{ + ifaces= + for i in $interface_order; do + for x in "$1"/$i.*; do + [ -f "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}" + done + done + for x in "$1"/*; do + [ -f "$x" ] && ifaces="$ifaces${ifaces:+ }${x##*/}" + done + uniqify $ifaces +} + +# Trim function +trim() +{ + var="$*" + var=${var#"${var%%[![:space:]]*}"} + var=${var%"${var##*[![:space:]]}"} + if [ -z "$var" ]; then + # So it seems our shell doesn't support wctype(3) patterns + # Fall back to sed + var=$(echo "$*" | sed -e 's/^[[:space:]]*//;s/[[:space:]]*$//') + fi + printf %s "$var" +} + +# We normally use sed to extract values using a key from a list of files +# but sed may not always be available at the time. +key_get_value() +{ + key="$1" + shift + + if type sed >/dev/null 2>&1; then + sed -n "s/^$key//p" $@ + else + for x do + while read line; do + case "$line" in + "$key"*) echo "${line##$key}";; + esac + done < "$x" + done + fi +} + +# We normally use sed to remove markers from a configuration file +# but sed may not always be available at the time. +remove_markers() +{ + m1="$1" + m2="$2" + in_marker=0 + + shift; shift + if type sed >/dev/null 2>&1; then + sed "/^$m1/,/^$m2/d" $@ + else + for x do + while read line; do + case "$line" in + "$m1"*) in_marker=1;; + "$m2"*) in_marker=0;; + *) [ $in_marker = 0 ] && echo "$line";; + esac + done < "$x" + done + fi +} + +# Compare two files. +comp_file() +{ + [ -e "$1" ] && [ -e "$2" ] || return 1 + + if type cmp >/dev/null 2>&1; then + cmp -s "$1" "$2" + elif type diff >/dev/null 2>&1; then + diff -q "$1" "$2" >/dev/null + else + # Hopefully we're only working on small text files ... + [ "$(cat "$1")" = "$(cat "$2")" ] + fi +} + +# Compare two files. +# If different, replace first with second otherwise remove second. +change_file() +{ + if [ -e "$1" ]; then + if comp_file "$1" "$2"; then + rm -f "$2" + return 1 + fi + fi + cat "$2" > "$1" + rm -f "$2" + return 0 +} + +# Compare two files. +# If different, copy or link depending on target type +copy_file() +{ + if [ -h "$2" ]; then + [ "$(readlink "$2")" = "$1" ] && return 1 + ln -sf "$1" "$2" + else + comp_file "$1" "$2" && return 1 + cat "$1" >"$2" + fi +} + +# Save a config file +save_conf() +{ + if [ -f "$1" ]; then + rm -f "$1-pre.$interface" + cat "$1" > "$1-pre.$interface" + fi +} + +# Restore a config file +restore_conf() +{ + [ -f "$1-pre.$interface" ] || return 1 + cat "$1-pre.$interface" > "$1" + rm -f "$1-pre.$interface" +} + +# Write a syslog entry +syslog() +{ + lvl="$1" + + if [ "$lvl" = debug ]; then + ${syslog_debug} || return 0 + fi + [ -n "$lvl" ] && shift + [ -n "$*" ] || return 0 + case "$lvl" in + err|error) echo "$interface: $*" >&2;; + *) echo "$interface: $*";; + esac + if type logger >/dev/null 2>&1; then + logger -i -p daemon."$lvl" -t dhcpcd-run-hooks "$interface: $*" + fi +} + +# Check for a valid name as per RFC952 and RFC1123 section 2.1 +valid_domainname() +{ + name="$1" + [ -z "$name" ] || [ ${#name} -gt 255 ] && return 1 + + while [ -n "$name" ]; do + label="${name%%.*}" + [ -z "$label" ] || [ ${#label} -gt 63 ] && return 1 + case "$label" in + -*|_*|*-|*_) return 1;; + *[![:alnum:]_-]*) return 1;; + "$name") return 0;; + esac + name="${name#*.}" + done + return 0 +} + +valid_domainname_list() +{ + for name do + valid_domainname "$name" || return $? + done + return 0 +} + +# With the advent of alternative init systems, it's possible to have +# more than one installed. So we need to try to guess what one we're +# using unless overridden by configure. +detect_init() +{ + _service_exists="@SERVICEEXISTS@" + _service_cmd="@SERVICECMD@" + _service_status="@SERVICESTATUS@" + + [ -n "$_service_cmd" ] && return 0 + + if $_detected_init; then + [ -n "$_service_cmd" ] + return $? + fi + + # Detect the running init system. + # As systemd and OpenRC can be installed on top of legacy init + # systems we try to detect them first. + status="@STATUSARG@" + : ${status:=status} + if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then + _service_exists="/bin/systemctl --quiet is-enabled \$1.service" + _service_status="/bin/systemctl --quiet is-active \$1.service" + _service_cmd="/bin/systemctl \$2 \$1.service" + elif [ -x /usr/bin/systemctl ] && [ -S /run/systemd/private ]; then + _service_exists="/usr/bin/systemctl --quiet is-enabled \$1.service" + _service_status="/usr/bin/systemctl --quiet is-active \$1.service" + _service_cmd="/usr/bin/systemctl \$2 \$1.service" + elif [ -x /sbin/rc-service ] && + { [ -s /libexec/rc/init.d/softlevel ] || + [ -s /run/openrc/softlevel ]; } + then + _service_exists="/sbin/rc-service -e \$1" + _service_cmd="/sbin/rc-service \$1 -- -D \$2" + elif [ -x /usr/sbin/invoke-rc.d ]; then + _service_exists="/usr/sbin/invoke-rc.d --query --quiet \$1 start >/dev/null 2>&1 || [ \$? = 104 ]" + _service_cmd="/usr/sbin/invoke-rc.d \$1 \$2" + elif [ -x /sbin/service ]; then + _service_exists="/sbin/service \$1 >/dev/null 2>&1" + _service_cmd="/sbin/service \$1 \$2" + elif [ -x /usr/sbin/service ]; then + _service_exists="/usr/sbin/service \$1 $status >/dev/null 2>&1" + _service_cmd="/usr/sbin/service \$1 \$2" + elif [ -x /bin/sv ]; then + _service_exists="/bin/sv status \$1 >/dev/null 2>&1" + _service_cmd="/bin/sv \$2 \$1" + elif [ -x /usr/bin/sv ]; then + _service_exists="/usr/bin/sv status \$1 >/dev/null 2>&1" + _service_cmd="/usr/bin/sv \$2 \$1" + elif [ -e /etc/slackware-version ] && [ -d /etc/rc.d ]; then + _service_exists="[ -x /etc/rc.d/rc.\$1 ]" + _service_cmd="/etc/rc.d/rc.\$1 \$2" + _service_status="/etc/rc.d/rc.\$1 status >/dev/null 2>&1" + else + for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do + if [ -d $x ]; then + _service_exists="[ -x $x/\$1 ]" + _service_cmd="$x/\$1 \$2" + _service_status="$x/\$1 $status >/dev/null 2>&1" + break + fi + done + if [ -e /etc/arch-release ]; then + _service_status="[ -e /var/run/daemons/\$1 ]" + elif [ "$x" = "/etc/rc.d" ] && [ -e /etc/rc.d/rc.subr ]; then + _service_status="$x/\$1 check >/dev/null 2>&1" + fi + fi + + _detected_init=true + if [ -z "$_service_cmd" ]; then + syslog err "could not detect a useable init system" + return 1 + fi + return 0 +} + +# Check a system service exists +service_exists() +{ + if [ -z "$_service_exists" ]; then + detect_init || return 1 + fi + eval $_service_exists +} + +# Send a command to a system service +service_cmd() +{ + if [ -z "$_service_cmd" ]; then + detect_init || return 1 + fi + eval $_service_cmd +} + +# Send a command to a system service if it is running +service_status() +{ + if [ -z "$_service_cmd" ]; then + detect_init || return 1 + fi + if [ -n "$_service_status" ]; then + eval $_service_status + else + service_command $1 status >/dev/null 2>&1 + fi +} + +# Handy macros for our hooks +service_command() +{ + service_exists $1 && service_cmd $1 $2 +} +service_condcommand() +{ + service_exists $1 && service_status $1 && service_cmd $1 $2 +} + +# We source each script into this one so that scripts run earlier can +# remove variables from the environment so later scripts don't see them. +# Thus, the user can create their dhcpcd.enter/exit-hook script to configure +# /etc/resolv.conf how they want and stop the system scripts ever updating it. +for hook in \ + @SYSCONFDIR@/dhcpcd.enter-hook \ + @HOOKDIR@/* \ + @SYSCONFDIR@/dhcpcd.exit-hook +do + for skip in $skip_hooks; do + case "$hook" in + */*~) continue 2;; + */"$skip") continue 2;; + */[0-9][0-9]"-$skip") continue 2;; + */[0-9][0-9]"-$skip.sh") continue 2;; + esac + done + if [ -f "$hook" ]; then + . "$hook" + fi +done diff --git a/iconfig.mk b/iconfig.mk new file mode 100644 index 000000000000..50c50340c17e --- /dev/null +++ b/iconfig.mk @@ -0,0 +1,8 @@ +# Nasty hack so that make clean works without configure being run +TOP?= . +_CONFIG_MK!= test -e ${TOP}/config.mk && \ + echo config.mk || echo config-null.mk +_CONFIG_MK?= $(shell test -e ${TOP}/config.mk && \ + echo config.mk || echo config-null.mk) +CONFIG_MK?= ${_CONFIG_MK} +include ${TOP}/${CONFIG_MK} diff --git a/src/GNUmakefile b/src/GNUmakefile new file mode 100644 index 000000000000..f66ad94c6e0e --- /dev/null +++ b/src/GNUmakefile @@ -0,0 +1,12 @@ +# GNU Make does not automagically include .depend +# Luckily it does read GNUmakefile over Makefile so we can work around it + +# Nasty hack so that make clean works without configure being run +TOP?= .. +CONFIG_MK?= $(shell test -e ${TOP}/config.mk && \ + echo config.mk || echo config-null.mk) + +include Makefile +ifneq ($(wildcard .depend), ) +include .depend +endif diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 000000000000..4849a77abd99 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,147 @@ +# dhcpcd Makefile + +PROG= dhcpcd +SRCS= common.c control.c dhcpcd.c duid.c eloop.c logerr.c +SRCS+= if.c if-options.c sa.c route.c +SRCS+= dhcp-common.c script.c + +CFLAGS?= -O2 +SUBDIRS+= ${MKDIRS} + +TOP= .. +include ${TOP}/iconfig.mk + +CSTD?= c99 +CFLAGS+= -std=${CSTD} +CPPFLAGS+= -I${TOP} -I${TOP}/src -I./crypt + +SRCS+= ${DHCPCD_SRCS} ${PRIVSEP_SRCS} +DHCPCD_DEF?= dhcpcd-definitions.conf +DHCPCD_DEFS= dhcpcd-definitions.conf dhcpcd-definitions-small.conf + +PCOMPAT_SRCS= ${COMPAT_SRCS:compat/%=${TOP}/compat/%} +PCRYPT_SRCS= ${CRYPT_SRCS:compat/%=${TOP}/compat/%} +OBJS+= ${SRCS:.c=.o} ${PCRYPT_SRCS:.c=.o} ${PCOMPAT_SRCS:.c=.o} + +MAN5= dhcpcd.conf.5 +MAN8= dhcpcd.8 +CLEANFILES= dhcpcd.conf.5 dhcpcd.8 + +FILES= dhcpcd.conf +FILESDIR= ${SYSCONFDIR} + +DEPEND!= test -e .depend && echo ".depend" || echo "" + +CLEANFILES+= *.tar.xz + +.PHONY: import import-bsd dev test + +.SUFFIXES: .in + +.in: Makefile ${TOP}/config.mk + ${SED} ${SED_RUNDIR} ${SED_DBDIR} ${SED_LIBDIR} ${SED_HOOKDIR} \ + ${SED_SYS} ${SED_SCRIPT} ${SED_DATADIR} \ + ${SED_SERVICEEXISTS} ${SED_SERVICECMD} ${SED_SERVICESTATUS} \ + ${SED_STATUSARG} \ + $< > $@ + +all: ${TOP}/config.h ${PROG} ${SCRIPTS} ${MAN5} ${MAN8} + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +dev: + cd dev && ${MAKE} + +.c.o: Makefile ${TOP}/config.mk + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +CLEANFILES+= dhcpcd-embedded.h dhcpcd-embedded.c + +dhcpcd-embedded.h: genembedh ${DHCPCD_DEFS} dhcpcd-embedded.h.in + ${HOST_SH} ${.ALLSRC} $^ > $@ + +dhcpcd-embedded.c: genembedc ${DHCPCD_DEFS} dhcpcd-embedded.c.in + ${HOST_SH} ${.ALLSRC} $^ > $@ + +if-options.c: dhcpcd-embedded.h + +.depend: ${SRCS} ${COMPAT_SRCS} ${CRYPT_SRCS} + ${CC} ${CPPFLAGS} -MM ${SRCS} ${COMPAT_SRCS} ${CRYPT_SRCS} > .depend + +depend: .depend + +${PROG}: ${DEPEND} ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} + +lint: + ${LINT} -Suz ${CPPFLAGS} ${SRCS} ${PCRYPT_SRCS} ${PCOMPAT_SRCS} + +_embeddedinstall: ${DHCPCD_DEF} + ${INSTALL} -d ${DESTDIR}${LIBEXECDIR} + ${INSTALL} -m ${CONFMODE} ${DHCPCD_DEF} ${DESTDIR}${LIBEXECDIR} + +_proginstall: ${PROG} + ${INSTALL} -d ${DESTDIR}${SBINDIR} + ${INSTALL} -m ${BINMODE} ${PROG} ${DESTDIR}${SBINDIR} + ${INSTALL} -m ${DBMODE} -d ${DESTDIR}${DBDIR} + +proginstall: _proginstall ${EMBEDDEDINSTALL} + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +_maninstall: ${MAN5} ${MAN8} + ${INSTALL} -d ${DESTDIR}${MANDIR}/man5 + ${INSTALL} -m ${MANMODE} ${MAN5} ${DESTDIR}${MANDIR}/man5 + ${INSTALL} -d ${DESTDIR}${MANDIR}/man8 + ${INSTALL} -m ${MANMODE} ${MAN8} ${DESTDIR}${MANDIR}/man8 + +_confinstall: + ${INSTALL} -d ${DESTDIR}${SYSCONFDIR} + # Install a new default config if not present + test -e ${DESTDIR}${SYSCONFDIR}/dhcpcd.conf || \ + ${INSTALL} -m ${CONFMODE} dhcpcd.conf ${DESTDIR}${SYSCONFDIR} + +eginstall: + +install: proginstall _maninstall _confinstall eginstall + +clean: + rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES} + for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@ || exit $$?; cd ..; done + +distclean: clean + rm -f .depend + rm -f *.diff *.patch *.orig *.rej + +_import-src: ${SRCS} ${MAN5} ${MAN8} + ${INSTALL} -d ${DESTDIR}/src + for x in defs.h ${SRCS} ${SRCS:.c=.h} dev.h ${MAN5} ${MAN8}; do \ + [ ! -e "$$x" ] || cp $$x ${DESTDIR}/src; \ + done + cp dhcpcd.conf ${DESTDIR}/src + if [ -n "${COMPAT_SRCS}" ]; then \ + ${INSTALL} -d ${DESTDIR}/compat; \ + for x in ${COMPAT_SRCS} ${COMPAT_SRCS:.c=.h}; do \ + [ ! -e "../$$x" ] || cp "../$$x" ${DESTDIR}/compat; \ + done; \ + fi + if ! grep HAVE_SYS_BITOPS_H ../config.h; then \ + cp ../compat/bitops.h ${DESTDIR}/compat; \ + fi + if grep compat/consttime_memequal.h ../config.h; then \ + cp ../compat/consttime_memequal.h ${DESTDIR}/compat; \ + fi + if [ -e ${DESTDIR}/compat/rb.c ]; then \ + cp ../compat/rbtree.h ${DESTDIR}/compat; \ + fi + if [ -e ${DESTDIR}/compat/strtoi.c ]; then \ + cp ../compat/_strtoi.h ${DESTDIR}/compat; \ + fi + if [ -n "${CRYPT_SRCS}" ]; then \ + ${INSTALL} -d ${DESTDIR}/compat/crypt; \ + for x in ${CRYPT_SRCS} ${CRYPT_SRCS:.c=.h}; do \ + cp "../$$x" ${DESTDIR}/compat/crypt; \ + done; \ + fi + # DragonFlyBSD builds base version with private crypto + if [ `uname` = DragonFly ]; then rm ${DESTDIR}/compat/crypt/md5* ${DESTDIR}/compat/crypt/sha256*; fi + +include ${TOP}/Makefile.inc diff --git a/src/arp.c b/src/arp.c new file mode 100644 index 000000000000..e8a27f42257b --- /dev/null +++ b/src/arp.c @@ -0,0 +1,634 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - ARP handler + * Copyright (c) 2006-2021 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. + */ + +#include <sys/socket.h> +#include <sys/types.h> + +#include <arpa/inet.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#define ELOOP_QUEUE ELOOP_ARP +#include "config.h" +#include "arp.h" +#include "bpf.h" +#include "ipv4.h" +#include "common.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv4ll.h" +#include "logerr.h" +#include "privsep.h" + +#if defined(ARP) +#define ARP_LEN \ + (FRAMEHDRLEN_MAX + \ + sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN)) + +/* ARP debugging can be quite noisy. Enable this for more noise! */ +//#define ARP_DEBUG + +/* Assert the correct structure size for on wire */ +__CTASSERT(sizeof(struct arphdr) == 8); + +static ssize_t +arp_request(const struct arp_state *astate, + const struct in_addr *sip) +{ + const struct interface *ifp = astate->iface; + const struct in_addr *tip = &astate->addr; + uint8_t arp_buffer[ARP_LEN]; + struct arphdr ar; + size_t len; + uint8_t *p; + + ar.ar_hrd = htons(ifp->hwtype); + ar.ar_pro = htons(ETHERTYPE_IP); + ar.ar_hln = ifp->hwlen; + ar.ar_pln = sizeof(tip->s_addr); + ar.ar_op = htons(ARPOP_REQUEST); + + p = arp_buffer; + len = 0; + +#define CHECK(fun, b, l) \ + do { \ + if (len + (l) > sizeof(arp_buffer)) \ + goto eexit; \ + fun(p, (b), (l)); \ + p += (l); \ + len += (l); \ + } while (/* CONSTCOND */ 0) +#define APPEND(b, l) CHECK(memcpy, b, l) +#define ZERO(l) CHECK(memset, 0, l) + + APPEND(&ar, sizeof(ar)); + APPEND(ifp->hwaddr, ifp->hwlen); + if (sip != NULL) + APPEND(&sip->s_addr, sizeof(sip->s_addr)); + else + ZERO(sizeof(tip->s_addr)); + ZERO(ifp->hwlen); + APPEND(&tip->s_addr, sizeof(tip->s_addr)); + +#ifdef PRIVSEP + if (ifp->ctx->options & DHCPCD_PRIVSEP) + return ps_bpf_sendarp(ifp, tip, arp_buffer, len); +#endif + /* Note that well formed ethernet will add extra padding + * to ensure that the packet is at least 60 bytes (64 including FCS). */ + return bpf_send(astate->bpf, ETHERTYPE_ARP, arp_buffer, len); + +eexit: + errno = ENOBUFS; + return -1; +} + +static void +arp_report_conflicted(const struct arp_state *astate, + const struct arp_msg *amsg) +{ + char abuf[HWADDR_LEN * 3]; + char fbuf[HWADDR_LEN * 3]; + + if (amsg == NULL) { + logerrx("%s: DAD detected %s", + astate->iface->name, inet_ntoa(astate->addr)); + return; + } + + hwaddr_ntoa(amsg->sha, astate->iface->hwlen, abuf, sizeof(abuf)); + if (bpf_frame_header_len(astate->iface) == 0) { + logwarnx("%s: %s claims %s", + astate->iface->name, abuf, inet_ntoa(astate->addr)); + return; + } + + logwarnx("%s: %s(%s) claims %s", + astate->iface->name, abuf, + hwaddr_ntoa(amsg->fsha, astate->iface->hwlen, fbuf, sizeof(fbuf)), + inet_ntoa(astate->addr)); +} + +static void +arp_found(struct arp_state *astate, const struct arp_msg *amsg) +{ + struct interface *ifp; + struct ipv4_addr *ia; +#ifndef KERNEL_RFC5227 + struct timespec now; +#endif + + arp_report_conflicted(astate, amsg); + ifp = astate->iface; + + /* If we haven't added the address we're doing a probe. */ + ia = ipv4_iffindaddr(ifp, &astate->addr, NULL); + if (ia == NULL) { + if (astate->found_cb != NULL) + astate->found_cb(astate, amsg); + return; + } + +#ifndef KERNEL_RFC5227 + /* RFC 3927 Section 2.5 says a defence should + * broadcast an ARP announcement. + * Because the kernel will also unicast a reply to the + * hardware address which requested the IP address + * the other IPv4LL client will receieve two ARP + * messages. + * If another conflict happens within DEFEND_INTERVAL + * then we must drop our address and negotiate a new one. */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (timespecisset(&astate->defend) && + eloop_timespec_diff(&now, &astate->defend, NULL) < DEFEND_INTERVAL) + logwarnx("%s: %d second defence failed for %s", + ifp->name, DEFEND_INTERVAL, inet_ntoa(astate->addr)); + else if (arp_request(astate, &astate->addr) == -1) + logerr(__func__); + else { + logdebugx("%s: defended address %s", + ifp->name, inet_ntoa(astate->addr)); + astate->defend = now; + return; + } +#endif + + if (astate->defend_failed_cb != NULL) + astate->defend_failed_cb(astate); +} + +static bool +arp_validate(const struct interface *ifp, struct arphdr *arp) +{ + + /* Address type must match */ + if (arp->ar_hrd != htons(ifp->hwtype)) + return false; + + /* Protocol must be IP. */ + if (arp->ar_pro != htons(ETHERTYPE_IP)) + return false; + + /* lladdr length matches */ + if (arp->ar_hln != ifp->hwlen) + return false; + + /* Protocol length must match in_addr_t */ + if (arp->ar_pln != sizeof(in_addr_t)) + return false; + + /* Only these types are recognised */ + if (arp->ar_op != htons(ARPOP_REPLY) && + arp->ar_op != htons(ARPOP_REQUEST)) + return false; + + return true; +} + +void +arp_packet(struct interface *ifp, uint8_t *data, size_t len, + unsigned int bpf_flags) +{ + size_t fl = bpf_frame_header_len(ifp), falen; + const struct interface *ifn; + struct arphdr ar; + struct arp_msg arm; + const struct iarp_state *state; + struct arp_state *astate, *astaten; + uint8_t *hw_s, *hw_t; + + /* Copy the frame header source and destination out */ + memset(&arm, 0, sizeof(arm)); + if (fl != 0) { + hw_s = bpf_frame_header_src(ifp, data, &falen); + if (hw_s != NULL && falen <= sizeof(arm.fsha)) + memcpy(arm.fsha, hw_s, falen); + hw_t = bpf_frame_header_dst(ifp, data, &falen); + if (hw_t != NULL && falen <= sizeof(arm.ftha)) + memcpy(arm.ftha, hw_t, falen); + + /* Skip past the frame header */ + data += fl; + len -= fl; + } + + /* We must have a full ARP header */ + if (len < sizeof(ar)) + return; + memcpy(&ar, data, sizeof(ar)); + + if (!arp_validate(ifp, &ar)) { +#ifdef BPF_DEBUG + logerrx("%s: ARP BPF validation failure", ifp->name); +#endif + return; + } + + /* Get pointers to the hardware addresses */ + hw_s = data + sizeof(ar); + hw_t = hw_s + ar.ar_hln + ar.ar_pln; + /* Ensure we got all the data */ + if ((size_t)((hw_t + ar.ar_hln + ar.ar_pln) - data) > len) + return; + /* Ignore messages from ourself */ + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ar.ar_hln == ifn->hwlen && + memcmp(hw_s, ifn->hwaddr, ifn->hwlen) == 0) + break; + } + if (ifn) { +#ifdef ARP_DEBUG + logdebugx("%s: ignoring ARP from self", ifp->name); +#endif + return; + } + /* Copy out the HW and IP addresses */ + memcpy(&arm.sha, hw_s, ar.ar_hln); + memcpy(&arm.sip.s_addr, hw_s + ar.ar_hln, ar.ar_pln); + memcpy(&arm.tha, hw_t, ar.ar_hln); + memcpy(&arm.tip.s_addr, hw_t + ar.ar_hln, ar.ar_pln); + + /* Match the ARP probe to our states. + * Ignore Unicast Poll, RFC1122. */ + state = ARP_CSTATE(ifp); + if (state == NULL) + return; + TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) { + if (IN_ARE_ADDR_EQUAL(&arm.sip, &astate->addr) || + (IN_IS_ADDR_UNSPECIFIED(&arm.sip) && + IN_ARE_ADDR_EQUAL(&arm.tip, &astate->addr) && + bpf_flags & BPF_BCAST)) + arp_found(astate, &arm); + } +} + +static void +arp_read(void *arg) +{ + struct arp_state *astate = arg; + struct bpf *bpf = astate->bpf; + struct interface *ifp = astate->iface; + uint8_t buf[ARP_LEN]; + ssize_t bytes; + struct in_addr addr = astate->addr; + + /* Some RAW mechanisms are generic file descriptors, not sockets. + * This means we have no kernel call to just get one packet, + * so we have to process the entire buffer. */ + bpf->bpf_flags &= ~BPF_EOF; + while (!(bpf->bpf_flags & BPF_EOF)) { + bytes = bpf_read(bpf, buf, sizeof(buf)); + if (bytes == -1) { + logerr("%s: %s", __func__, ifp->name); + arp_free(astate); + return; + } + arp_packet(ifp, buf, (size_t)bytes, bpf->bpf_flags); + /* Check we still have a state after processing. */ + if ((astate = arp_find(ifp, &addr)) == NULL) + break; + if ((bpf = astate->bpf) == NULL) + break; + } +} + +static void +arp_probed(void *arg) +{ + struct arp_state *astate = arg; + + timespecclear(&astate->defend); + astate->not_found_cb(astate); +} + +static void +arp_probe1(void *arg) +{ + struct arp_state *astate = arg; + struct interface *ifp = astate->iface; + unsigned int delay; + + if (++astate->probes < PROBE_NUM) { + delay = (PROBE_MIN * MSEC_PER_SEC) + + (arc4random_uniform( + (PROBE_MAX - PROBE_MIN) * MSEC_PER_SEC)); + eloop_timeout_add_msec(ifp->ctx->eloop, delay, arp_probe1, astate); + } else { + delay = ANNOUNCE_WAIT * MSEC_PER_SEC; + eloop_timeout_add_msec(ifp->ctx->eloop, delay, arp_probed, astate); + } + logdebugx("%s: ARP probing %s (%d of %d), next in %0.1f seconds", + ifp->name, inet_ntoa(astate->addr), + astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM, + (float)delay / MSEC_PER_SEC); + if (arp_request(astate, NULL) == -1) + logerr(__func__); +} + +void +arp_probe(struct arp_state *astate) +{ + + astate->probes = 0; + logdebugx("%s: probing for %s", + astate->iface->name, inet_ntoa(astate->addr)); + arp_probe1(astate); +} +#endif /* ARP */ + +struct arp_state * +arp_find(struct interface *ifp, const struct in_addr *addr) +{ + struct iarp_state *state; + struct arp_state *astate; + + if ((state = ARP_STATE(ifp)) == NULL) + goto out; + TAILQ_FOREACH(astate, &state->arp_states, next) { + if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp) + return astate; + } +out: + errno = ESRCH; + return NULL; +} + +static void +arp_announced(void *arg) +{ + struct arp_state *astate = arg; + + if (astate->announced_cb) { + astate->announced_cb(astate); + return; + } + + /* Keep the ARP state open to handle ongoing ACD. */ +} + +static void +arp_announce1(void *arg) +{ + struct arp_state *astate = arg; + struct interface *ifp = astate->iface; + struct ipv4_addr *ia; + + if (++astate->claims < ANNOUNCE_NUM) + logdebugx("%s: ARP announcing %s (%d of %d), " + "next in %d.0 seconds", + ifp->name, inet_ntoa(astate->addr), + astate->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT); + else + logdebugx("%s: ARP announcing %s (%d of %d)", + ifp->name, inet_ntoa(astate->addr), + astate->claims, ANNOUNCE_NUM); + + /* The kernel will send a Gratuitous ARP for newly added addresses. + * So we can avoid sending the same. + * Linux is special and doesn't send one. */ + ia = ipv4_iffindaddr(ifp, &astate->addr, NULL); +#ifndef __linux__ + if (astate->claims == 1 && ia != NULL && ia->flags & IPV4_AF_NEW) + goto skip_request; +#endif + + if (arp_request(astate, &astate->addr) == -1) + logerr(__func__); + +#ifndef __linux__ +skip_request: +#endif + /* No longer a new address. */ + if (ia != NULL) + ia->flags |= ~IPV4_AF_NEW; + + eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT, + astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced, + astate); +} + +static void +arp_announce(struct arp_state *astate) +{ + struct iarp_state *state; + struct interface *ifp; + struct arp_state *a2; + int r; + + /* Cancel any other ARP announcements for this address. */ + TAILQ_FOREACH(ifp, astate->iface->ctx->ifaces, next) { + state = ARP_STATE(ifp); + if (state == NULL) + continue; + TAILQ_FOREACH(a2, &state->arp_states, next) { + if (astate == a2 || + a2->addr.s_addr != astate->addr.s_addr) + continue; + r = eloop_timeout_delete(a2->iface->ctx->eloop, + a2->claims < ANNOUNCE_NUM + ? arp_announce1 : arp_announced, + a2); + if (r == -1) + logerr(__func__); + else if (r != 0) { + logdebugx("%s: ARP announcement " + "of %s cancelled", + a2->iface->name, + inet_ntoa(a2->addr)); + arp_announced(a2); + } + } + } + + astate->claims = 0; + arp_announce1(astate); +} + +struct arp_state * +arp_ifannounceaddr(struct interface *ifp, const struct in_addr *ia) +{ + struct arp_state *astate; + + if (ifp->flags & IFF_NOARP || !(ifp->options->options & DHCPCD_ARP)) + return NULL; + + astate = arp_find(ifp, ia); + if (astate == NULL) { + astate = arp_new(ifp, ia); + if (astate == NULL) + return NULL; + astate->announced_cb = arp_free; + } + arp_announce(astate); + return astate; +} + +struct arp_state * +arp_announceaddr(struct dhcpcd_ctx *ctx, const struct in_addr *ia) +{ + struct interface *ifp, *iff = NULL; + struct ipv4_addr *iap; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (!ifp->active || !if_is_link_up(ifp)) + continue; + iap = ipv4_iffindaddr(ifp, ia, NULL); + if (iap == NULL) + continue; +#ifdef IN_IFF_NOTUSEABLE + if (iap->addr_flags & IN_IFF_NOTUSEABLE) + continue; +#endif + if (iff != NULL && iff->metric < ifp->metric) + continue; + iff = ifp; + } + if (iff == NULL) + return NULL; + + return arp_ifannounceaddr(iff, ia); +} + +struct arp_state * +arp_new(struct interface *ifp, const struct in_addr *addr) +{ + struct iarp_state *state; + struct arp_state *astate; + + if ((state = ARP_STATE(ifp)) == NULL) { + ifp->if_data[IF_DATA_ARP] = malloc(sizeof(*state)); + state = ARP_STATE(ifp); + if (state == NULL) { + logerr(__func__); + return NULL; + } + TAILQ_INIT(&state->arp_states); + } else { + if ((astate = arp_find(ifp, addr)) != NULL) + return astate; + } + + if ((astate = calloc(1, sizeof(*astate))) == NULL) { + logerr(__func__); + return NULL; + } + astate->iface = ifp; + astate->addr = *addr; + +#ifdef PRIVSEP + if (IN_PRIVSEP(ifp->ctx)) { + if (ps_bpf_openarp(ifp, addr) == -1) { + logerr(__func__); + free(astate); + return NULL; + } + } else +#endif + { + astate->bpf = bpf_open(ifp, bpf_arp, addr); + if (astate->bpf == NULL) { + logerr(__func__); + free(astate); + return NULL; + } + eloop_event_add(ifp->ctx->eloop, astate->bpf->bpf_fd, + arp_read, astate); + } + + + state = ARP_STATE(ifp); + TAILQ_INSERT_TAIL(&state->arp_states, astate, next); + return astate; +} + +void +arp_free(struct arp_state *astate) +{ + struct interface *ifp; + struct dhcpcd_ctx *ctx; + struct iarp_state *state; + + if (astate == NULL) + return; + + ifp = astate->iface; + ctx = ifp->ctx; + eloop_timeout_delete(ctx->eloop, NULL, astate); + + state = ARP_STATE(ifp); + TAILQ_REMOVE(&state->arp_states, astate, next); + if (astate->free_cb) + astate->free_cb(astate); + +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx) && ps_bpf_closearp(ifp, &astate->addr) == -1) + logerr(__func__); +#endif + if (astate->bpf != NULL) { + eloop_event_delete(ctx->eloop, astate->bpf->bpf_fd); + bpf_close(astate->bpf); + } + + free(astate); + + if (TAILQ_FIRST(&state->arp_states) == NULL) { + free(state); + ifp->if_data[IF_DATA_ARP] = NULL; + } +} + +void +arp_freeaddr(struct interface *ifp, const struct in_addr *ia) +{ + struct arp_state *astate; + + astate = arp_find(ifp, ia); + arp_free(astate); +} + +void +arp_drop(struct interface *ifp) +{ + struct iarp_state *state; + struct arp_state *astate; + + while ((state = ARP_STATE(ifp)) != NULL && + (astate = TAILQ_FIRST(&state->arp_states)) != NULL) + arp_free(astate); +} diff --git a/src/arp.h b/src/arp.h new file mode 100644 index 000000000000..0ac8ef711622 --- /dev/null +++ b/src/arp.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#ifndef ARP_H +#define ARP_H + +/* ARP timings from RFC5227 */ +#define PROBE_WAIT 1 +#define PROBE_NUM 3 +#define PROBE_MIN 1 +#define PROBE_MAX 2 +#define ANNOUNCE_WAIT 2 +#define ANNOUNCE_NUM 2 +#define ANNOUNCE_INTERVAL 2 +#define MAX_CONFLICTS 10 +#define RATE_LIMIT_INTERVAL 60 +#define DEFEND_INTERVAL 10 + +#include "bpf.h" +#include "dhcpcd.h" +#include "if.h" + +#ifdef IN_IFF_DUPLICATED +/* NetBSD gained RFC 5227 support in the kernel. + * This means dhcpcd doesn't need ARP except for ARPing support + * and ARP announcing an address. */ +#if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 799003900 +#define KERNEL_RFC5227 +#endif +#endif + +struct arp_msg { + uint16_t op; + uint8_t sha[HWADDR_LEN]; + struct in_addr sip; + uint8_t tha[HWADDR_LEN]; + struct in_addr tip; + /* Frame header and sender to diagnose failures */ + uint8_t fsha[HWADDR_LEN]; + uint8_t ftha[HWADDR_LEN]; +}; + +struct arp_state { + TAILQ_ENTRY(arp_state) next; + struct interface *iface; + struct in_addr addr; + struct bpf *bpf; + + int probes; + int claims; + struct timespec defend; + + void (*found_cb)(struct arp_state *, const struct arp_msg *); + void (*not_found_cb)(struct arp_state *); + void (*announced_cb)(struct arp_state *); + void (*defend_failed_cb)(struct arp_state *); + void (*free_cb)(struct arp_state *); +}; +TAILQ_HEAD(arp_statehead, arp_state); + +struct iarp_state { + struct arp_statehead arp_states; +}; + +#define ARP_STATE(ifp) \ + ((struct iarp_state *)(ifp)->if_data[IF_DATA_ARP]) +#define ARP_CSTATE(ifp) \ + ((const struct iarp_state *)(ifp)->if_data[IF_DATA_ARP]) + +#ifdef ARP +void arp_packet(struct interface *, uint8_t *, size_t, unsigned int); +struct arp_state *arp_new(struct interface *, const struct in_addr *); +void arp_probe(struct arp_state *); +struct arp_state *arp_announceaddr(struct dhcpcd_ctx *, const struct in_addr *); +struct arp_state *arp_ifannounceaddr(struct interface *, const struct in_addr *); +struct arp_state * arp_find(struct interface *, const struct in_addr *); +void arp_free(struct arp_state *); +void arp_freeaddr(struct interface *, const struct in_addr *); +void arp_drop(struct interface *); +#endif /* ARP */ +#endif /* ARP_H */ diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 000000000000..bfb2b5dda57a --- /dev/null +++ b/src/auth.c @@ -0,0 +1,738 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#include <sys/file.h> +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "auth.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "dhcpcd.h" +#include "privsep-root.h" + +#ifdef HAVE_HMAC_H +#include <hmac.h> +#endif + +#ifdef __sun +#define htonll +#define ntohll +#endif + +#ifndef htonll +#if (BYTE_ORDER == LITTLE_ENDIAN) +#define htonll(x) ((uint64_t)htonl((uint32_t)((x) >> 32)) | \ + (uint64_t)htonl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32) +#else /* (BYTE_ORDER == LITTLE_ENDIAN) */ +#define htonll(x) (x) +#endif +#endif /* htonll */ + +#ifndef ntohll +#if (BYTE_ORDER == LITTLE_ENDIAN) +#define ntohll(x) ((uint64_t)ntohl((uint32_t)((x) >> 32)) | \ + (uint64_t)ntohl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32) +#else /* (BYTE_ORDER == LITTLE_ENDIAN) */ +#define ntohll(x) (x) +#endif +#endif /* ntohll */ + +#define HMAC_LENGTH 16 + +void +dhcp_auth_reset(struct authstate *state) +{ + + state->replay = 0; + if (state->token) { + free(state->token->key); + free(state->token->realm); + free(state->token); + state->token = NULL; + } + if (state->reconf) { + free(state->reconf->key); + free(state->reconf->realm); + free(state->reconf); + state->reconf = NULL; + } +} + +/* + * Authenticate a DHCP message. + * m and mlen refer to the whole message. + * t is the DHCP type, pass it 4 or 6. + * data and dlen refer to the authentication option within the message. + */ +const struct token * +dhcp_auth_validate(struct authstate *state, const struct auth *auth, + const void *vm, size_t mlen, int mp, int mt, + const void *vdata, size_t dlen) +{ + const uint8_t *m, *data; + uint8_t protocol, algorithm, rdm, *mm, type; + uint64_t replay; + uint32_t secretid; + const uint8_t *d, *realm; + size_t realm_len; + const struct token *t; + time_t now; + uint8_t hmac_code[HMAC_LENGTH]; + + if (dlen < 3 + sizeof(replay)) { + errno = EINVAL; + return NULL; + } + + m = vm; + data = vdata; + /* Ensure that d is inside m which *may* not be the case for DHCPv4. + * This can occur if the authentication option is split using + * DHCP long option from RFC 3399. Section 9 which does infact note that + * implementations should take this into account. + * Fixing this would be problematic, patches welcome. */ + if (data < m || data > m + mlen || data + dlen > m + mlen) { + errno = ERANGE; + return NULL; + } + + d = data; + protocol = *d++; + algorithm = *d++; + rdm = *d++; + if (!(auth->options & DHCPCD_AUTH_SEND)) { + /* If we didn't send any authorisation, it can only be a + * reconfigure key */ + if (protocol != AUTH_PROTO_RECONFKEY) { + errno = EINVAL; + return NULL; + } + } else if (protocol != auth->protocol || + algorithm != auth->algorithm || + rdm != auth->rdm) + { + /* As we don't require authentication, we should still + * accept a reconfigure key */ + if (protocol != AUTH_PROTO_RECONFKEY || + auth->options & DHCPCD_AUTH_REQUIRE) + { + errno = EPERM; + return NULL; + } + } + dlen -= 3; + + memcpy(&replay, d, sizeof(replay)); + replay = ntohll(replay); + /* + * Test for a replay attack. + * + * NOTE: Some servers always send a replay data value of zero. + * This is strictly compliant with RFC 3315 and 3318 which say: + * "If the RDM field contains 0x00, the replay detection field MUST be + * set to the value of a monotonically increasing counter." + * An example of a monotonically increasing sequence is: + * 1, 2, 2, 2, 2, 2, 2 + * Errata 3474 updates RFC 3318 to say: + * "If the RDM field contains 0x00, the replay detection field MUST be + * set to the value of a strictly increasing counter." + * + * Taking the above into account, dhcpcd will only test for + * strictly speaking replay attacks if it receives any non zero + * replay data to validate against. + */ + if (state->token && state->replay != 0) { + if (state->replay == (replay ^ 0x8000000000000000ULL)) { + /* We don't know if the singular point is increasing + * or decreasing. */ + errno = EPERM; + return NULL; + } + if ((uint64_t)(replay - state->replay) <= 0) { + /* Replay attack detected */ + errno = EPERM; + return NULL; + } + } + d+= sizeof(replay); + dlen -= sizeof(replay); + + realm = NULL; + realm_len = 0; + + /* Extract realm and secret. + * Rest of data is MAC. */ + switch (protocol) { + case AUTH_PROTO_TOKEN: + secretid = auth->token_rcv_secretid; + break; + case AUTH_PROTO_DELAYED: + if (dlen < sizeof(secretid) + sizeof(hmac_code)) { + errno = EINVAL; + return NULL; + } + memcpy(&secretid, d, sizeof(secretid)); + secretid = ntohl(secretid); + d += sizeof(secretid); + dlen -= sizeof(secretid); + break; + case AUTH_PROTO_DELAYEDREALM: + if (dlen < sizeof(secretid) + sizeof(hmac_code)) { + errno = EINVAL; + return NULL; + } + realm_len = dlen - (sizeof(secretid) + sizeof(hmac_code)); + if (realm_len) { + realm = d; + d += realm_len; + dlen -= realm_len; + } + memcpy(&secretid, d, sizeof(secretid)); + secretid = ntohl(secretid); + d += sizeof(secretid); + dlen -= sizeof(secretid); + break; + case AUTH_PROTO_RECONFKEY: + if (dlen != 1 + 16) { + errno = EINVAL; + return NULL; + } + type = *d++; + dlen--; + switch (type) { + case 1: + if ((mp == 4 && mt == DHCP_ACK) || + (mp == 6 && mt == DHCP6_REPLY)) + { + if (state->reconf == NULL) { + state->reconf = + malloc(sizeof(*state->reconf)); + if (state->reconf == NULL) + return NULL; + state->reconf->key = malloc(16); + if (state->reconf->key == NULL) { + free(state->reconf); + state->reconf = NULL; + return NULL; + } + state->reconf->secretid = 0; + state->reconf->expire = 0; + state->reconf->realm = NULL; + state->reconf->realm_len = 0; + state->reconf->key_len = 16; + } + memcpy(state->reconf->key, d, 16); + } else { + errno = EINVAL; + return NULL; + } + if (state->reconf == NULL) + errno = ENOENT; + /* Free the old token so we log acceptance */ + if (state->token) { + free(state->token); + state->token = NULL; + } + /* Nothing to validate, just accepting the key */ + return state->reconf; + case 2: + if (!((mp == 4 && mt == DHCP_FORCERENEW) || + (mp == 6 && mt == DHCP6_RECONFIGURE))) + { + errno = EINVAL; + return NULL; + } + if (state->reconf == NULL) { + errno = ENOENT; + return NULL; + } + t = state->reconf; + goto gottoken; + default: + errno = EINVAL; + return NULL; + } + default: + errno = ENOTSUP; + return NULL; + } + + /* Find a token for the realm and secret */ + TAILQ_FOREACH(t, &auth->tokens, next) { + if (t->secretid == secretid && + t->realm_len == realm_len && + (t->realm_len == 0 || + memcmp(t->realm, realm, t->realm_len) == 0)) + break; + } + if (t == NULL) { + errno = ESRCH; + return NULL; + } + if (t->expire) { + if (time(&now) == -1) + return NULL; + if (t->expire < now) { + errno = EFAULT; + return NULL; + } + } + +gottoken: + /* First message from the server */ + if (state->token && + (state->token->secretid != t->secretid || + state->token->realm_len != t->realm_len || + memcmp(state->token->realm, t->realm, t->realm_len))) + { + errno = EPERM; + return NULL; + } + + /* Special case as no hashing needs to be done. */ + if (protocol == AUTH_PROTO_TOKEN) { + if (dlen != t->key_len || memcmp(d, t->key, dlen)) { + errno = EPERM; + return NULL; + } + goto finish; + } + + /* Make a duplicate of the message, but zero out the MAC part */ + mm = malloc(mlen); + if (mm == NULL) + return NULL; + memcpy(mm, m, mlen); + memset(mm + (d - m), 0, dlen); + + /* RFC3318, section 5.2 - zero giaddr and hops */ + if (mp == 4) { + /* Assert the bootp structure is correct size. */ + __CTASSERT(sizeof(struct bootp) == 300); + + *(mm + offsetof(struct bootp, hops)) = '\0'; + memset(mm + offsetof(struct bootp, giaddr), 0, 4); + } + + memset(hmac_code, 0, sizeof(hmac_code)); + switch (algorithm) { + case AUTH_ALG_HMAC_MD5: + hmac("md5", t->key, t->key_len, mm, mlen, + hmac_code, sizeof(hmac_code)); + break; + default: + errno = ENOSYS; + free(mm); + return NULL; + } + + free(mm); + if (!consttime_memequal(d, &hmac_code, dlen)) { + errno = EPERM; + return NULL; + } + +finish: + /* If we got here then authentication passed */ + state->replay = replay; + if (state->token == NULL) { + /* We cannot just save a pointer because a reconfigure will + * recreate the token list. So we duplicate it. */ + state->token = malloc(sizeof(*state->token)); + if (state->token) { + state->token->secretid = t->secretid; + state->token->key = malloc(t->key_len); + if (state->token->key) { + state->token->key_len = t->key_len; + memcpy(state->token->key, t->key, t->key_len); + } else { + free(state->token); + state->token = NULL; + return NULL; + } + if (t->realm_len) { + state->token->realm = malloc(t->realm_len); + if (state->token->realm) { + state->token->realm_len = t->realm_len; + memcpy(state->token->realm, t->realm, + t->realm_len); + } else { + free(state->token->key); + free(state->token); + state->token = NULL; + return NULL; + } + } else { + state->token->realm = NULL; + state->token->realm_len = 0; + } + } + /* If we cannot save the token, we must invalidate */ + if (state->token == NULL) + return NULL; + } + + return t; +} + +int +auth_get_rdm_monotonic(uint64_t *rdm) +{ + FILE *fp; + int err; +#ifdef LOCK_EX + int flocked; +#endif + + fp = fopen(RDM_MONOFILE, "r+"); + if (fp == NULL) { + if (errno != ENOENT) + return -1; + fp = fopen(RDM_MONOFILE, "w"); + if (fp == NULL) + return -1; + if (chmod(RDM_MONOFILE, 0400) == -1) { + fclose(fp); + unlink(RDM_MONOFILE); + return -1; + } +#ifdef LOCK_EX + flocked = flock(fileno(fp), LOCK_EX); +#endif + *rdm = 0; + } else { +#ifdef LOCK_EX + flocked = flock(fileno(fp), LOCK_EX); +#endif + if (fscanf(fp, "0x%016" PRIu64, rdm) != 1) { + fclose(fp); + return -1; + } + } + + (*rdm)++; + if (fseek(fp, 0, SEEK_SET) == -1 || + ftruncate(fileno(fp), 0) == -1 || + fprintf(fp, "0x%016" PRIu64 "\n", *rdm) != 19 || + fflush(fp) == EOF) + err = -1; + else + err = 0; +#ifdef LOCK_EX + if (flocked == 0) + flock(fileno(fp), LOCK_UN); +#endif + fclose(fp); + return err; +} + +#define NTP_EPOCH 2208988800U /* 1970 - 1900 in seconds */ +#define NTP_SCALE_FRAC 4294967295.0 /* max value of the fractional part */ +static uint64_t +get_next_rdm_monotonic_clock(struct auth *auth) +{ + struct timespec ts; + uint64_t secs, rdm; + double frac; + + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) + return ++auth->last_replay; /* report error? */ + + secs = (uint64_t)ts.tv_sec + NTP_EPOCH; + frac = ((double)ts.tv_nsec / 1e9 * NTP_SCALE_FRAC); + rdm = (secs << 32) | (uint64_t)frac; + return rdm; +} + +static uint64_t +get_next_rdm_monotonic(struct dhcpcd_ctx *ctx, struct auth *auth) +{ +#ifndef PRIVSEP + UNUSED(ctx); +#endif + + if (auth->options & DHCPCD_AUTH_RDM_COUNTER) { + uint64_t rdm; + int err; + +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx)) { + + err = ps_root_getauthrdm(ctx, &rdm); + } else +#endif + err = auth_get_rdm_monotonic(&rdm); + if (err == -1) + return ++auth->last_replay; + + auth->last_replay = rdm; + return rdm; + } + return get_next_rdm_monotonic_clock(auth); +} + +/* + * Encode a DHCP message. + * Either we know which token to use from the server response + * or we are using a basic configuration token. + * token is the token to encrypt with. + * m and mlen refer to the whole message. + * mp is the DHCP type, pass it 4 or 6. + * mt is the DHCP message type. + * data and dlen refer to the authentication option within the message. + */ +ssize_t +dhcp_auth_encode(struct dhcpcd_ctx *ctx, struct auth *auth, + const struct token *t, + void *vm, size_t mlen, int mp, int mt, + void *vdata, size_t dlen) +{ + uint64_t rdm; + uint8_t hmac_code[HMAC_LENGTH]; + time_t now; + uint8_t hops, *p, *m, *data; + uint32_t giaddr, secretid; + bool auth_info; + + /* Ignore the token argument given to us - always send using the + * configured token. */ + if (auth->protocol == AUTH_PROTO_TOKEN) { + TAILQ_FOREACH(t, &auth->tokens, next) { + if (t->secretid == auth->token_snd_secretid) + break; + } + if (t == NULL) { + errno = EINVAL; + return -1; + } + if (t->expire) { + if (time(&now) == -1) + return -1; + if (t->expire < now) { + errno = EPERM; + return -1; + } + } + } + + switch(auth->protocol) { + case AUTH_PROTO_TOKEN: + case AUTH_PROTO_DELAYED: + case AUTH_PROTO_DELAYEDREALM: + /* We don't ever send a reconf key */ + break; + default: + errno = ENOTSUP; + return -1; + } + + switch(auth->algorithm) { + case AUTH_ALG_NONE: + case AUTH_ALG_HMAC_MD5: + break; + default: + errno = ENOTSUP; + return -1; + } + + switch(auth->rdm) { + case AUTH_RDM_MONOTONIC: + break; + default: + errno = ENOTSUP; + return -1; + } + + /* DISCOVER or INFORM messages don't write auth info */ + if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) || + (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ))) + auth_info = false; + else + auth_info = true; + + /* Work out the auth area size. + * We only need to do this for DISCOVER messages */ + if (vdata == NULL) { + dlen = 1 + 1 + 1 + 8; + switch(auth->protocol) { + case AUTH_PROTO_TOKEN: + dlen += t->key_len; + break; + case AUTH_PROTO_DELAYEDREALM: + if (auth_info && t) + dlen += t->realm_len; + /* FALLTHROUGH */ + case AUTH_PROTO_DELAYED: + if (auth_info && t) + dlen += sizeof(t->secretid) + sizeof(hmac_code); + break; + } + return (ssize_t)dlen; + } + + if (dlen < 1 + 1 + 1 + 8) { + errno = ENOBUFS; + return -1; + } + + /* Ensure that d is inside m which *may* not be the case for DHPCPv4 */ + m = vm; + data = vdata; + if (data < m || data > m + mlen || data + dlen > m + mlen) { + errno = ERANGE; + return -1; + } + + /* Write out our option */ + *data++ = auth->protocol; + *data++ = auth->algorithm; + /* + * RFC 3315 21.4.4.1 says that SOLICIT in DELAYED authentication + * should not set RDM or it's data. + * An expired draft draft-ietf-dhc-dhcpv6-clarify-auth-01 suggets + * this should not be set for INFORMATION REQ messages as well, + * which is probably a good idea because both states start from zero. + */ + if (auth_info || + !(auth->protocol & (AUTH_PROTO_DELAYED | AUTH_PROTO_DELAYEDREALM))) + { + *data++ = auth->rdm; + switch (auth->rdm) { + case AUTH_RDM_MONOTONIC: + rdm = get_next_rdm_monotonic(ctx, auth); + break; + default: + /* This block appeases gcc, clang doesn't need it */ + rdm = get_next_rdm_monotonic(ctx, auth); + break; + } + rdm = htonll(rdm); + memcpy(data, &rdm, 8); + } else { + *data++ = 0; /* rdm */ + memset(data, 0, 8); /* replay detection data */ + } + data += 8; + dlen -= 1 + 1 + 1 + 8; + + /* Special case as no hashing needs to be done. */ + if (auth->protocol == AUTH_PROTO_TOKEN) { + /* Should be impossible, but still */ + if (t == NULL) { + errno = EINVAL; + return -1; + } + if (dlen < t->key_len) { + errno = ENOBUFS; + return -1; + } + memcpy(data, t->key, t->key_len); + return (ssize_t)(dlen - t->key_len); + } + + /* DISCOVER or INFORM messages don't write auth info */ + if (!auth_info) + return (ssize_t)dlen; + + /* Loading a saved lease without an authentication option */ + if (t == NULL) + return 0; + + /* Write out the Realm */ + if (auth->protocol == AUTH_PROTO_DELAYEDREALM) { + if (dlen < t->realm_len) { + errno = ENOBUFS; + return -1; + } + memcpy(data, t->realm, t->realm_len); + data += t->realm_len; + dlen -= t->realm_len; + } + + /* Write out the SecretID */ + if (auth->protocol == AUTH_PROTO_DELAYED || + auth->protocol == AUTH_PROTO_DELAYEDREALM) + { + if (dlen < sizeof(t->secretid)) { + errno = ENOBUFS; + return -1; + } + secretid = htonl(t->secretid); + memcpy(data, &secretid, sizeof(secretid)); + data += sizeof(secretid); + dlen -= sizeof(secretid); + } + + /* Zero what's left, the MAC */ + memset(data, 0, dlen); + + /* RFC3318, section 5.2 - zero giaddr and hops */ + if (mp == 4) { + p = m + offsetof(struct bootp, hops); + hops = *p; + *p = '\0'; + p = m + offsetof(struct bootp, giaddr); + memcpy(&giaddr, p, sizeof(giaddr)); + memset(p, 0, sizeof(giaddr)); + } else { + /* appease GCC again */ + hops = 0; + giaddr = 0; + } + + /* Create our hash and write it out */ + switch(auth->algorithm) { + case AUTH_ALG_HMAC_MD5: + hmac("md5", t->key, t->key_len, m, mlen, + hmac_code, sizeof(hmac_code)); + memcpy(data, hmac_code, sizeof(hmac_code)); + break; + } + + /* RFC3318, section 5.2 - restore giaddr and hops */ + if (mp == 4) { + p = m + offsetof(struct bootp, hops); + *p = hops; + p = m + offsetof(struct bootp, giaddr); + memcpy(p, &giaddr, sizeof(giaddr)); + } + + /* Done! */ + return (int)(dlen - sizeof(hmac_code)); /* should be zero */ +} diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 000000000000..c6eb428512f6 --- /dev/null +++ b/src/auth.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#ifndef AUTH_H +#define AUTH_H + +#include "config.h" + +#ifdef HAVE_SYS_QUEUE_H +#include <sys/queue.h> +#endif + +#define DHCPCD_AUTH_SEND (1 << 0) +#define DHCPCD_AUTH_REQUIRE (1 << 1) +#define DHCPCD_AUTH_RDM_COUNTER (1 << 2) + +#define DHCPCD_AUTH_SENDREQUIRE (DHCPCD_AUTH_SEND | DHCPCD_AUTH_REQUIRE) + +#define AUTH_PROTO_TOKEN 0 +#define AUTH_PROTO_DELAYED 1 +#define AUTH_PROTO_DELAYEDREALM 2 +#define AUTH_PROTO_RECONFKEY 3 + +#define AUTH_ALG_NONE 0 +#define AUTH_ALG_HMAC_MD5 1 + +#define AUTH_RDM_MONOTONIC 0 + +struct token { + TAILQ_ENTRY(token) next; + uint32_t secretid; + size_t realm_len; + unsigned char *realm; + size_t key_len; + unsigned char *key; + time_t expire; +}; + +TAILQ_HEAD(token_head, token); + +struct auth { + int options; +#ifdef AUTH + uint8_t protocol; + uint8_t algorithm; + uint8_t rdm; + uint64_t last_replay; + uint8_t last_replay_set; + struct token_head tokens; + uint32_t token_snd_secretid; + uint32_t token_rcv_secretid; +#endif +}; + +struct authstate { + uint64_t replay; + struct token *token; + struct token *reconf; +}; + +void dhcp_auth_reset(struct authstate *); + +const struct token * dhcp_auth_validate(struct authstate *, + const struct auth *, + const void *, size_t, int, int, + const void *, size_t); + +struct dhcpcd_ctx; +ssize_t dhcp_auth_encode(struct dhcpcd_ctx *, struct auth *, + const struct token *, + void *, size_t, int, int, + void *, size_t); + +int auth_get_rdm_monotonic(uint64_t *rdm); +#endif diff --git a/src/bpf.c b/src/bpf.c new file mode 100644 index 000000000000..21b73af207be --- /dev/null +++ b/src/bpf.c @@ -0,0 +1,713 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd: BPF arp and bootp filtering + * Copyright (c) 2006-2021 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. + */ + +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <arpa/inet.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#ifdef __linux__ +/* Special BPF snowflake. */ +#include <linux/filter.h> +#define bpf_insn sock_filter +#else +#include <net/bpf.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include "common.h" +#include "arp.h" +#include "bpf.h" +#include "dhcp.h" +#include "if.h" +#include "logerr.h" + +/* BPF helper macros */ +#ifdef __linux__ +#define BPF_WHOLEPACKET 0x7fffffff /* work around buggy LPF filters */ +#else +#define BPF_WHOLEPACKET ~0U +#endif + +/* Macros to update the BPF structure */ +#define BPF_SET_STMT(insn, c, v) { \ + (insn)->code = (c); \ + (insn)->jt = 0; \ + (insn)->jf = 0; \ + (insn)->k = (uint32_t)(v); \ +} + +#define BPF_SET_JUMP(insn, c, v, t, f) { \ + (insn)->code = (c); \ + (insn)->jt = (t); \ + (insn)->jf = (f); \ + (insn)->k = (uint32_t)(v); \ +} + +size_t +bpf_frame_header_len(const struct interface *ifp) +{ + + switch (ifp->hwtype) { + case ARPHRD_ETHER: + return sizeof(struct ether_header); + default: + return 0; + } +} + +void * +bpf_frame_header_src(const struct interface *ifp, void *fh, size_t *len) +{ + uint8_t *f = fh; + + switch (ifp->hwtype) { + case ARPHRD_ETHER: + *len = sizeof(((struct ether_header *)0)->ether_shost); + return f + offsetof(struct ether_header, ether_shost); + default: + *len = 0; + errno = ENOTSUP; + return NULL; + } +} + +void * +bpf_frame_header_dst(const struct interface *ifp, void *fh, size_t *len) +{ + uint8_t *f = fh; + + switch (ifp->hwtype) { + case ARPHRD_ETHER: + *len = sizeof(((struct ether_header *)0)->ether_dhost); + return f + offsetof(struct ether_header, ether_dhost); + default: + *len = 0; + errno = ENOTSUP; + return NULL; + } +} + +static const uint8_t etherbcastaddr[] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +int +bpf_frame_bcast(const struct interface *ifp, const void *frame) +{ + + switch (ifp->hwtype) { + case ARPHRD_ETHER: + return memcmp((const char *)frame + + offsetof(struct ether_header, ether_dhost), + etherbcastaddr, sizeof(etherbcastaddr)); + default: + return -1; + } +} + +#ifndef __linux__ +/* Linux is a special snowflake for opening, attaching and reading BPF. + * See if-linux.c for the Linux specific BPF functions. */ + +const char *bpf_name = "Berkley Packet Filter"; + +struct bpf * +bpf_open(const struct interface *ifp, + int (*filter)(const struct bpf *, const struct in_addr *), + const struct in_addr *ia) +{ + struct bpf *bpf; + struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 }; + struct ifreq ifr = { .ifr_flags = 0 }; + int ibuf_len = 0; +#ifdef BIOCIMMEDIATE + unsigned int flags; +#endif +#ifndef O_CLOEXEC + int fd_opts; +#endif + + bpf = calloc(1, sizeof(*bpf)); + if (bpf == NULL) + return NULL; + bpf->bpf_ifp = ifp; + +#ifdef _PATH_BPF + bpf->bpf_fd = open(_PATH_BPF, O_RDWR | O_NONBLOCK +#ifdef O_CLOEXEC + | O_CLOEXEC +#endif + ); +#else + char device[32]; + int n = 0; + + do { + snprintf(device, sizeof(device), "/dev/bpf%d", n++); + bpf->bpf_fd = open(device, O_RDWR | O_NONBLOCK +#ifdef O_CLOEXEC + | O_CLOEXEC +#endif + ); + } while (bpf->bpf_fd == -1 && errno == EBUSY); +#endif + + if (bpf->bpf_fd == -1) + goto eexit; + +#ifndef O_CLOEXEC + if ((fd_opts = fcntl(bpf->bpf_fd, F_GETFD)) == -1 || + fcntl(bpf->bpf_fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1) + goto eexit; +#endif + + if (ioctl(bpf->bpf_fd, BIOCVERSION, &pv) == -1) + goto eexit; + if (pv.bv_major != BPF_MAJOR_VERSION || + pv.bv_minor < BPF_MINOR_VERSION) { + logerrx("BPF version mismatch - recompile"); + goto eexit; + } + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (ioctl(bpf->bpf_fd, BIOCSETIF, &ifr) == -1) + goto eexit; + +#ifdef BIOCIMMEDIATE + flags = 1; + if (ioctl(bpf->bpf_fd, BIOCIMMEDIATE, &flags) == -1) + goto eexit; +#endif + + if (filter(bpf, ia) != 0) + goto eexit; + + /* Get the required BPF buffer length from the kernel. */ + if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1) + goto eexit; + bpf->bpf_size = (size_t)ibuf_len; + bpf->bpf_buffer = malloc(bpf->bpf_size); + if (bpf->bpf_buffer == NULL) + goto eexit; + return bpf; + +eexit: + if (bpf->bpf_fd != -1) + close(bpf->bpf_fd); + free(bpf); + return NULL; +} + +/* BPF requires that we read the entire buffer. + * So we pass the buffer in the API so we can loop on >1 packet. */ +ssize_t +bpf_read(struct bpf *bpf, void *data, size_t len) +{ + ssize_t bytes; + struct bpf_hdr packet; + const char *payload; + + bpf->bpf_flags &= ~BPF_EOF; + for (;;) { + if (bpf->bpf_len == 0) { + bytes = read(bpf->bpf_fd, bpf->bpf_buffer, + bpf->bpf_size); +#if defined(__sun) + /* After 2^31 bytes, the kernel offset overflows. + * To work around this bug, lseek 0. */ + if (bytes == -1 && errno == EINVAL) { + lseek(bpf->bpf_fd, 0, SEEK_SET); + continue; + } +#endif + if (bytes == -1 || bytes == 0) + return bytes; + bpf->bpf_len = (size_t)bytes; + bpf->bpf_pos = 0; + } + bytes = -1; + payload = (const char *)bpf->bpf_buffer + bpf->bpf_pos; + memcpy(&packet, payload, sizeof(packet)); + if (bpf->bpf_pos + packet.bh_caplen + packet.bh_hdrlen > + bpf->bpf_len) + goto next; /* Packet beyond buffer, drop. */ + payload += packet.bh_hdrlen; + if (packet.bh_caplen > len) + bytes = (ssize_t)len; + else + bytes = (ssize_t)packet.bh_caplen; + if (bpf_frame_bcast(bpf->bpf_ifp, payload) == 0) + bpf->bpf_flags |= BPF_BCAST; + else + bpf->bpf_flags &= ~BPF_BCAST; + memcpy(data, payload, (size_t)bytes); +next: + bpf->bpf_pos += BPF_WORDALIGN(packet.bh_hdrlen + + packet.bh_caplen); + if (bpf->bpf_pos >= bpf->bpf_len) { + bpf->bpf_len = bpf->bpf_pos = 0; + bpf->bpf_flags |= BPF_EOF; + } + if (bytes != -1) + return bytes; + } + + /* NOTREACHED */ +} + +int +bpf_attach(int fd, void *filter, unsigned int filter_len) +{ + struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len }; + + /* Install the filter. */ + return ioctl(fd, BIOCSETF, &pf); +} + +#ifdef BIOCSETWF +static int +bpf_wattach(int fd, void *filter, unsigned int filter_len) +{ + struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len }; + + /* Install the filter. */ + return ioctl(fd, BIOCSETWF, &pf); +} +#endif +#endif + +#ifndef __sun +/* SunOS is special too - sending via BPF goes nowhere. */ +ssize_t +bpf_send(const struct bpf *bpf, uint16_t protocol, + const void *data, size_t len) +{ + struct iovec iov[2]; + struct ether_header eh; + + switch(bpf->bpf_ifp->hwtype) { + case ARPHRD_ETHER: + memset(&eh.ether_dhost, 0xff, sizeof(eh.ether_dhost)); + memcpy(&eh.ether_shost, bpf->bpf_ifp->hwaddr, + sizeof(eh.ether_shost)); + eh.ether_type = htons(protocol); + iov[0].iov_base = &eh; + iov[0].iov_len = sizeof(eh); + break; + default: + iov[0].iov_base = NULL; + iov[0].iov_len = 0; + break; + } + iov[1].iov_base = UNCONST(data); + iov[1].iov_len = len; + return writev(bpf->bpf_fd, iov, 2); +} +#endif + +void +bpf_close(struct bpf *bpf) +{ + + close(bpf->bpf_fd); + free(bpf->bpf_buffer); + free(bpf); +} + +#ifdef ARP +#define BPF_CMP_HWADDR_LEN ((((HWADDR_LEN / 4) + 2) * 2) + 1) +static unsigned int +bpf_cmp_hwaddr(struct bpf_insn *bpf, size_t bpf_len, size_t off, + bool equal, const uint8_t *hwaddr, size_t hwaddr_len) +{ + struct bpf_insn *bp; + size_t maclen, nlft, njmps; + uint32_t mac32; + uint16_t mac16; + uint8_t jt, jf; + + /* Calc the number of jumps */ + if ((hwaddr_len / 4) >= 128) { + errno = EINVAL; + return 0; + } + njmps = (hwaddr_len / 4) * 2; /* 2 instructions per check */ + /* We jump after the 1st check. */ + if (njmps) + njmps -= 2; + nlft = hwaddr_len % 4; + if (nlft) { + njmps += (nlft / 2) * 2; + nlft = nlft % 2; + if (nlft) + njmps += 2; + + } + + /* Skip to positive finish. */ + njmps++; + if (equal) { + jt = (uint8_t)njmps; + jf = 0; + } else { + jt = 0; + jf = (uint8_t)njmps; + } + + bp = bpf; + for (; hwaddr_len > 0; + hwaddr += maclen, hwaddr_len -= maclen, off += maclen) + { + if (bpf_len < 3) { + errno = ENOBUFS; + return 0; + } + bpf_len -= 3; + + if (hwaddr_len >= 4) { + maclen = sizeof(mac32); + memcpy(&mac32, hwaddr, maclen); + BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, off); + bp++; + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, + htonl(mac32), jt, jf); + } else if (hwaddr_len >= 2) { + maclen = sizeof(mac16); + memcpy(&mac16, hwaddr, maclen); + BPF_SET_STMT(bp, BPF_LD + BPF_H + BPF_IND, off); + bp++; + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, + htons(mac16), jt, jf); + } else { + maclen = sizeof(*hwaddr); + BPF_SET_STMT(bp, BPF_LD + BPF_B + BPF_IND, off); + bp++; + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, + *hwaddr, jt, jf); + } + if (jt) + jt = (uint8_t)(jt - 2); + if (jf) + jf = (uint8_t)(jf - 2); + bp++; + } + + /* Last step is always return failure. + * Next step is a positive finish. */ + BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); + bp++; + + return (unsigned int)(bp - bpf); +} +#endif + +#ifdef ARP +static const struct bpf_insn bpf_arp_ether [] = { + /* Check this is an ARP packet. */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, + offsetof(struct ether_header, ether_type)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + + /* Load frame header length into X */ + BPF_STMT(BPF_LDX + BPF_W + BPF_IMM, sizeof(struct ether_header)), + + /* Make sure the hardware type matches. */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_hrd)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + + /* Make sure the hardware length matches. */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct arphdr, ar_hln)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, + sizeof(((struct ether_arp *)0)->arp_sha), 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), +}; +#define BPF_ARP_ETHER_LEN __arraycount(bpf_arp_ether) + +static const struct bpf_insn bpf_arp_filter [] = { + /* Make sure this is for IP. */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_pro)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + /* Make sure this is an ARP REQUEST. */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_op)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), + /* or ARP REPLY. */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + /* Make sure the protocol length matches. */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct arphdr, ar_pln)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(in_addr_t), 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), +}; +#define BPF_ARP_FILTER_LEN __arraycount(bpf_arp_filter) + +/* One address is two checks of two statements. */ +#define BPF_NADDRS 1 +#define BPF_ARP_ADDRS_LEN 5 + ((BPF_NADDRS * 2) * 2) + +#define BPF_ARP_LEN BPF_ARP_ETHER_LEN + BPF_ARP_FILTER_LEN + \ + BPF_CMP_HWADDR_LEN + BPF_ARP_ADDRS_LEN + +static int +bpf_arp_rw(const struct bpf *bpf, const struct in_addr *ia, bool recv) +{ + const struct interface *ifp = bpf->bpf_ifp; + struct bpf_insn buf[BPF_ARP_LEN + 1]; + struct bpf_insn *bp; + uint16_t arp_len; + + bp = buf; + /* Check frame header. */ + switch(ifp->hwtype) { + case ARPHRD_ETHER: + memcpy(bp, bpf_arp_ether, sizeof(bpf_arp_ether)); + bp += BPF_ARP_ETHER_LEN; + arp_len = sizeof(struct ether_header)+sizeof(struct ether_arp); + break; + default: + errno = EINVAL; + return -1; + } + + /* Copy in the main filter. */ + memcpy(bp, bpf_arp_filter, sizeof(bpf_arp_filter)); + bp += BPF_ARP_FILTER_LEN; + + /* Ensure it's not from us. */ + bp += bpf_cmp_hwaddr(bp, BPF_CMP_HWADDR_LEN, sizeof(struct arphdr), + !recv, ifp->hwaddr, ifp->hwlen); + + /* Match sender protocol address */ + BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, + sizeof(struct arphdr) + ifp->hwlen); + bp++; + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1); + bp++; + BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); + bp++; + + /* If we didn't match sender, then we're only interested in + * ARP probes to us, so check the null host sender. */ + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, INADDR_ANY, 1, 0); + bp++; + BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); + bp++; + + /* Match target protocol address */ + BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, (sizeof(struct arphdr) + + (size_t)(ifp->hwlen * 2) + sizeof(in_addr_t))); + bp++; + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1); + bp++; + BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); + bp++; + + /* No match, drop it */ + BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); + bp++; + +#ifdef BIOCSETWF + if (!recv) + return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); +#endif + + return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); +} + +int +bpf_arp(const struct bpf *bpf, const struct in_addr *ia) +{ + +#ifdef BIOCSETWF + if (bpf_arp_rw(bpf, ia, true) == -1 || + bpf_arp_rw(bpf, ia, false) == -1 || + ioctl(bpf->bpf_fd, BIOCLOCK) == -1) + return -1; + return 0; +#else + return bpf_arp_rw(bpf, ia, true); +#endif +} +#endif + +#ifdef ARPHRD_NONE +static const struct bpf_insn bpf_bootp_none[] = { +}; +#define BPF_BOOTP_NONE_LEN __arraycount(bpf_bootp_none) +#endif + +static const struct bpf_insn bpf_bootp_ether[] = { + /* Make sure this is an IP packet. */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, + offsetof(struct ether_header, ether_type)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + + /* Advance to the IP header. */ + BPF_STMT(BPF_LDX + BPF_K, sizeof(struct ether_header)), +}; +#define BPF_BOOTP_ETHER_LEN __arraycount(bpf_bootp_ether) + +static const struct bpf_insn bpf_bootp_base[] = { + /* Make sure it's an IPv4 packet. */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0), + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0xf0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x40, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + + /* Make sure it's a UDP packet. */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(struct ip, ip_p)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + + /* Make sure this isn't a fragment. */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct ip, ip_off)), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 0, 1), + BPF_STMT(BPF_RET + BPF_K, 0), + + /* Advance to the UDP header. */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0), + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x0f), + BPF_STMT(BPF_ALU + BPF_MUL + BPF_K, 4), + BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), + BPF_STMT(BPF_MISC + BPF_TAX, 0), +}; +#define BPF_BOOTP_BASE_LEN __arraycount(bpf_bootp_base) + +static const struct bpf_insn bpf_bootp_read[] = { + /* Make sure it's from and to the right port. */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPS << 16) + BOOTPC, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), +}; +#define BPF_BOOTP_READ_LEN __arraycount(bpf_bootp_read) + +#ifdef BIOCSETWF +static const struct bpf_insn bpf_bootp_write[] = { + /* Make sure it's from and to the right port. */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPC << 16) + BOOTPS, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), +}; +#define BPF_BOOTP_WRITE_LEN __arraycount(bpf_bootp_write) +#endif + +#define BPF_BOOTP_CHADDR_LEN ((BOOTP_CHADDR_LEN / 4) * 3) +#define BPF_BOOTP_XID_LEN 4 /* BOUND check is 4 instructions */ + +#define BPF_BOOTP_LEN BPF_BOOTP_ETHER_LEN + \ + BPF_BOOTP_BASE_LEN + BPF_BOOTP_READ_LEN + \ + BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4 + +static int +bpf_bootp_rw(const struct bpf *bpf, bool read) +{ + struct bpf_insn buf[BPF_BOOTP_LEN + 1]; + struct bpf_insn *bp; + + bp = buf; + /* Check frame header. */ + switch(bpf->bpf_ifp->hwtype) { +#ifdef ARPHRD_NONE + case ARPHRD_NONE: + memcpy(bp, bpf_bootp_none, sizeof(bpf_bootp_none)); + bp += BPF_BOOTP_NONE_LEN; + break; +#endif + case ARPHRD_ETHER: + memcpy(bp, bpf_bootp_ether, sizeof(bpf_bootp_ether)); + bp += BPF_BOOTP_ETHER_LEN; + break; + default: + errno = EINVAL; + return -1; + } + + /* Copy in the main filter. */ + memcpy(bp, bpf_bootp_base, sizeof(bpf_bootp_base)); + bp += BPF_BOOTP_BASE_LEN; + +#ifdef BIOCSETWF + if (!read) { + memcpy(bp, bpf_bootp_write, sizeof(bpf_bootp_write)); + bp += BPF_BOOTP_WRITE_LEN; + + /* All passed, return the packet. */ + BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET); + bp++; + + return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); + } +#else + UNUSED(read); +#endif + + memcpy(bp, bpf_bootp_read, sizeof(bpf_bootp_read)); + bp += BPF_BOOTP_READ_LEN; + + /* All passed, return the packet. */ + BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET); + bp++; + + return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); +} + +int +bpf_bootp(const struct bpf *bpf, __unused const struct in_addr *ia) +{ + +#ifdef BIOCSETWF + if (bpf_bootp_rw(bpf, true) == -1 || + bpf_bootp_rw(bpf, false) == -1 || + ioctl(bpf->bpf_fd, BIOCLOCK) == -1) + return -1; + return 0; +#else +#ifdef PRIVSEP +#if defined(__sun) /* Solaris cannot send via BPF. */ +#elif defined(BIOCSETF) +#warning No BIOCSETWF support - a compromised BPF can be used as a raw socket +#else +#warning A compromised PF_PACKET socket can be used as a raw socket +#endif +#endif + return bpf_bootp_rw(bpf, true); +#endif +} diff --git a/src/bpf.h b/src/bpf.h new file mode 100644 index 000000000000..40da95e61ee6 --- /dev/null +++ b/src/bpf.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd: BPF arp and bootp filtering + * Copyright (c) 2006-2021 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. + */ + +#ifndef BPF_HEADER +#define BPF_HEADER + +#define BPF_EOF 0x01U +#define BPF_PARTIALCSUM 0x02U +#define BPF_BCAST 0x04U + +/* + * Even though we program the BPF filter should we trust it? + * On Linux at least there is a window between opening the socket, + * binding the interface and setting the filter where we receive data. + * This data is NOT checked OR flushed and IS returned when reading. + * We have no way of flushing it other than reading these packets! + * But we don't know if they passed the filter or not ..... so we need + * to validate each and every packet that comes through ourselves as well. + * Even if Linux does fix this sorry state, who is to say other kernels + * don't have bugs causing a similar effect? + * + * As such, let's strive to keep the filters just for pattern matching + * to avoid waking dhcpcd up. + * + * If you want to be notified of any packet failing the BPF filter, + * define BPF_DEBUG below. + */ +//#define BPF_DEBUG + +#include "dhcpcd.h" + +struct bpf { + const struct interface *bpf_ifp; + int bpf_fd; + unsigned int bpf_flags; + void *bpf_buffer; + size_t bpf_size; + size_t bpf_len; + size_t bpf_pos; +}; + +extern const char *bpf_name; +size_t bpf_frame_header_len(const struct interface *); +void *bpf_frame_header_src(const struct interface *, void *, size_t *); +void *bpf_frame_header_dst(const struct interface *, void *, size_t *); +int bpf_frame_bcast(const struct interface *, const void *); +struct bpf * bpf_open(const struct interface *, + int (*)(const struct bpf *, const struct in_addr *), + const struct in_addr *); +void bpf_close(struct bpf *); +int bpf_attach(int, void *, unsigned int); +ssize_t bpf_send(const struct bpf *, uint16_t, const void *, size_t); +ssize_t bpf_read(struct bpf *, void *, size_t); +int bpf_arp(const struct bpf *, const struct in_addr *); +int bpf_bootp(const struct bpf *, const struct in_addr *); +#endif diff --git a/src/common.c b/src/common.c new file mode 100644 index 000000000000..bb89100ef2cf --- /dev/null +++ b/src/common.c @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#include <sys/stat.h> +#include <sys/statvfs.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "common.h" +#include "dhcpcd.h" +#include "if-options.h" + +const char * +hwaddr_ntoa(const void *hwaddr, size_t hwlen, char *buf, size_t buflen) +{ + const unsigned char *hp, *ep; + char *p; + + if (buf == NULL || hwlen == 0) + return NULL; + + if (hwlen * 3 > buflen) { + errno = ENOBUFS; + return NULL; + } + + hp = hwaddr; + ep = hp + hwlen; + p = buf; + + while (hp < ep) { + if (hp != hwaddr) + *p ++= ':'; + p += snprintf(p, 3, "%.2x", *hp++); + } + *p ++= '\0'; + return buf; +} + +size_t +hwaddr_aton(uint8_t *buffer, const char *addr) +{ + char c[3]; + const char *p = addr; + uint8_t *bp = buffer; + size_t len = 0; + + c[2] = '\0'; + while (*p != '\0') { + /* Skip separators */ + c[0] = *p++; + switch (c[0]) { + case '\n': /* long duid split on lines */ + case ':': /* typical mac address */ + case '-': /* uuid */ + continue; + } + c[1] = *p++; + /* Ensure that digits are hex */ + if (isxdigit((unsigned char)c[0]) == 0 || + isxdigit((unsigned char)c[1]) == 0) + { + errno = EINVAL; + return 0; + } + /* We should have at least two entries 00:01 */ + if (len == 0 && *p == '\0') { + errno = EINVAL; + return 0; + } + if (bp) + *bp++ = (uint8_t)strtol(c, NULL, 16); + len++; + } + return len; +} + +ssize_t +readfile(const char *file, void *data, size_t len) +{ + int fd; + ssize_t bytes; + + fd = open(file, O_RDONLY); + if (fd == -1) + return -1; + bytes = read(fd, data, len); + close(fd); + if ((size_t)bytes == len) { + errno = ENOBUFS; + return -1; + } + return bytes; +} + +ssize_t +writefile(const char *file, mode_t mode, const void *data, size_t len) +{ + int fd; + ssize_t bytes; + + fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode); + if (fd == -1) + return -1; + bytes = write(fd, data, len); + close(fd); + return bytes; +} + +int +filemtime(const char *file, time_t *time) +{ + struct stat st; + + if (stat(file, &st) == -1) + return -1; + *time = st.st_mtime; + return 0; +} + +/* Handy routine to read very long lines in text files. + * This means we read the whole line and avoid any nasty buffer overflows. + * We strip leading space and avoid comment lines, making the code that calls + * us smaller. */ +char * +get_line(char ** __restrict buf, ssize_t * __restrict buflen) +{ + char *p, *c; + bool quoted; + + do { + p = *buf; + if (*buf == NULL) + return NULL; + c = memchr(*buf, '\n', (size_t)*buflen); + if (c == NULL) { + c = memchr(*buf, '\0', (size_t)*buflen); + if (c == NULL) + return NULL; + *buflen = c - *buf; + *buf = NULL; + } else { + *c++ = '\0'; + *buflen -= c - *buf; + *buf = c; + } + for (; *p == ' ' || *p == '\t'; p++) + ; + } while (*p == '\0' || *p == '\n' || *p == '#' || *p == ';'); + + /* Strip embedded comments unless in a quoted string or escaped */ + quoted = false; + for (c = p; *c != '\0'; c++) { + if (*c == '\\') { + c++; /* escaped */ + continue; + } + if (*c == '"') + quoted = !quoted; + else if (*c == '#' && !quoted) { + *c = '\0'; + break; + } + } + return p; +} + + +int +is_root_local(void) +{ +#ifdef ST_LOCAL + struct statvfs vfs; + + if (statvfs("/", &vfs) == -1) + return -1; + return vfs.f_flag & ST_LOCAL ? 1 : 0; +#else + errno = ENOTSUP; + return -1; +#endif +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 000000000000..ff8f3f8b943b --- /dev/null +++ b/src/common.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#ifndef COMMON_H +#define COMMON_H + +#include <sys/param.h> +#include <sys/time.h> +#include <sys/types.h> +#include <stdint.h> +#include <stdio.h> + +/* Define eloop queues here, as other apps share eloop.h */ +#define ELOOP_DHCPCD 1 /* default queue */ +#define ELOOP_DHCP 2 +#define ELOOP_ARP 3 +#define ELOOP_IPV4LL 4 +#define ELOOP_IPV6 5 +#define ELOOP_IPV6ND 6 +#define ELOOP_IPV6RA_EXPIRE 7 +#define ELOOP_DHCP6 8 +#define ELOOP_IF 9 + +#ifndef HOSTNAME_MAX_LEN +#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ +#endif + +#ifndef MIN +#define MIN(a,b) ((/*CONSTCOND*/(a)<(b))?(a):(b)) +#define MAX(a,b) ((/*CONSTCOND*/(a)>(b))?(a):(b)) +#endif + +#define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) +#define STRINGIFY(a) #a +#define TOSTRING(a) STRINGIFY(a) +#define UNUSED(a) (void)(a) + +#define ROUNDUP4(a) (1 + (((a) - 1) | 3)) +#define ROUNDUP8(a) (1 + (((a) - 1) | 7)) + +/* Some systems don't define timespec macros */ +#ifndef timespecclear +#define timespecclear(tsp) (tsp)->tv_sec = (time_t)((tsp)->tv_nsec = 0L) +#define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) +#endif + +#if __GNUC__ > 2 || defined(__INTEL_COMPILER) +# ifndef __packed +# define __packed __attribute__((__packed__)) +# endif +# ifndef __unused +# define __unused __attribute__((__unused__)) +# endif +#else +# ifndef __packed +# define __packed +# endif +# ifndef __unused +# define __unused +# endif +#endif + +/* Needed for rbtree(3) compat */ +#ifndef __RCSID +#define __RCSID(a) +#endif +#ifndef __predict_false +# if __GNUC__ > 2 +# define __predict_true(exp) __builtin_expect((exp) != 0, 1) +# define __predict_false(exp) __builtin_expect((exp) != 0, 0) +#else +# define __predict_true(exp) (exp) +# define __predict_false(exp) (exp) +# endif +#endif +#ifndef __BEGIN_DECLS +# if defined(__cplusplus) +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS }; +# else /* __BEGIN_DECLS */ +# define __BEGIN_DECLS +# define __END_DECLS +# endif /* __BEGIN_DECLS */ +#endif /* __BEGIN_DECLS */ + +#ifndef __fallthrough +# if __GNUC__ >= 7 +# define __fallthrough __attribute__((fallthrough)) +# else +# define __fallthrough +# endif +#endif + +/* + * Compile Time Assertion. + */ +#ifndef __CTASSERT +# ifdef __COUNTER__ +# define __CTASSERT(x) __CTASSERT0(x, __ctassert, __COUNTER__) +# else +# define __CTASSERT(x) __CTASSERT99(x, __INCLUDE_LEVEL__, __LINE__) +# define __CTASSERT99(x, a, b) __CTASSERT0(x, __CONCAT(__ctassert,a), \ + __CONCAT(_,b)) +# endif +# define __CTASSERT0(x, y, z) __CTASSERT1(x, y, z) +# define __CTASSERT1(x, y, z) typedef char y ## z[/*CONSTCOND*/(x) ? 1 : -1] __unused +#endif + +#ifndef __arraycount +# define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) +#endif + +/* We don't really need this as our supported systems define __restrict + * automatically for us, but it is here for completeness. */ +#ifndef __restrict +# if defined(__lint__) +# define __restrict +# elif __STDC_VERSION__ >= 199901L +# define __restrict restrict +# elif !(2 < __GNUC__ || (2 == __GNU_C && 95 <= __GNUC_VERSION__)) +# define __restrict +# endif +#endif + +const char *hwaddr_ntoa(const void *, size_t, char *, size_t); +size_t hwaddr_aton(uint8_t *, const char *); +ssize_t readfile(const char *, void *, size_t); +ssize_t writefile(const char *, mode_t, const void *, size_t); +int filemtime(const char *, time_t *); +char *get_line(char ** __restrict, ssize_t * __restrict); +int is_root_local(void); +#endif diff --git a/src/control.c b/src/control.c new file mode 100644 index 000000000000..4d768d887b9f --- /dev/null +++ b/src/control.c @@ -0,0 +1,594 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/un.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcpcd.h" +#include "control.h" +#include "eloop.h" +#include "if.h" +#include "logerr.h" +#include "privsep.h" + +#ifndef SUN_LEN +#define SUN_LEN(su) \ + (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) +#endif + +static void +control_queue_free(struct fd_list *fd) +{ + struct fd_data *fdp; + + while ((fdp = TAILQ_FIRST(&fd->queue))) { + TAILQ_REMOVE(&fd->queue, fdp, next); + if (fdp->data_size != 0) + free(fdp->data); + free(fdp); + } + +#ifdef CTL_FREE_LIST + while ((fdp = TAILQ_FIRST(&fd->free_queue))) { + TAILQ_REMOVE(&fd->free_queue, fdp, next); + if (fdp->data_size != 0) + free(fdp->data); + free(fdp); + } +#endif +} + +void +control_free(struct fd_list *fd) +{ + +#ifdef PRIVSEP + if (fd->ctx->ps_control_client == fd) + fd->ctx->ps_control_client = NULL; +#endif + + if (eloop_event_remove_writecb(fd->ctx->eloop, fd->fd) == -1) + logerr(__func__); + TAILQ_REMOVE(&fd->ctx->control_fds, fd, next); + control_queue_free(fd); + free(fd); +} + +void +control_delete(struct fd_list *fd) +{ + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(fd->ctx)) + return; +#endif + + eloop_event_delete(fd->ctx->eloop, fd->fd); + close(fd->fd); + control_free(fd); +} + +static void +control_handle_data(void *arg) +{ + struct fd_list *fd = arg; + char buffer[1024]; + ssize_t bytes; + + bytes = read(fd->fd, buffer, sizeof(buffer) - 1); + + if (bytes == -1 || bytes == 0) { + /* Control was closed or there was an error. + * Remove it from our list. */ + control_delete(fd); + return; + } + +#ifdef PRIVSEP + if (IN_PRIVSEP(fd->ctx)) { + ssize_t err; + + fd->flags |= FD_SENDLEN; + err = ps_ctl_handleargs(fd, buffer, (size_t)bytes); + fd->flags &= ~FD_SENDLEN; + if (err == -1) { + logerr(__func__); + return; + } + if (err == 1 && + ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) { + logerr(__func__); + control_delete(fd); + } + return; + } +#endif + + control_recvdata(fd, buffer, (size_t)bytes); +} + +void +control_recvdata(struct fd_list *fd, char *data, size_t len) +{ + char *p = data, *e; + char *argvp[255], **ap; + int argc; + + /* Each command is \n terminated + * Each argument is NULL separated */ + while (len != 0) { + argc = 0; + ap = argvp; + while (len != 0) { + if (*p == '\0') { + p++; + len--; + continue; + } + e = memchr(p, '\0', len); + if (e == NULL) { + errno = EINVAL; + logerrx("%s: no terminator", __func__); + return; + } + if ((size_t)argc >= sizeof(argvp) / sizeof(argvp[0])) { + errno = ENOBUFS; + logerrx("%s: no arg buffer", __func__); + return; + } + *ap++ = p; + argc++; + e++; + len -= (size_t)(e - p); + p = e; + e--; + if (*(--e) == '\n') { + *e = '\0'; + break; + } + } + if (argc == 0) { + logerrx("%s: no args", __func__); + continue; + } + *ap = NULL; + if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) { + logerr(__func__); + if (errno != EINTR && errno != EAGAIN) { + control_delete(fd); + return; + } + } + } +} + +struct fd_list * +control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags) +{ + struct fd_list *l; + + l = malloc(sizeof(*l)); + if (l == NULL) + return NULL; + + l->ctx = ctx; + l->fd = fd; + l->flags = flags; + TAILQ_INIT(&l->queue); +#ifdef CTL_FREE_LIST + TAILQ_INIT(&l->free_queue); +#endif + TAILQ_INSERT_TAIL(&ctx->control_fds, l, next); + return l; +} + +static void +control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags) +{ + struct sockaddr_un run; + socklen_t len; + struct fd_list *l; + int fd, flags; + + len = sizeof(run); + if ((fd = accept(lfd, (struct sockaddr *)&run, &len)) == -1) + goto error; + if ((flags = fcntl(fd, F_GETFD, 0)) == -1 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + goto error; + if ((flags = fcntl(fd, F_GETFL, 0)) == -1 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + goto error; + +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx) && !IN_PRIVSEP_SE(ctx)) + ; + else +#endif + fd_flags |= FD_SENDLEN; + + l = control_new(ctx, fd, fd_flags); + if (l == NULL) + goto error; + + if (eloop_event_add(ctx->eloop, l->fd, control_handle_data, l) == -1) + logerr(__func__); + return; + +error: + logerr(__func__); + if (fd != -1) + close(fd); +} + +static void +control_handle(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + control_handle1(ctx, ctx->control_fd, 0); +} + +static void +control_handle_unpriv(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + control_handle1(ctx, ctx->control_unpriv_fd, FD_UNPRIV); +} + +static int +make_path(char *path, size_t len, const char *ifname, sa_family_t family, + bool unpriv) +{ + const char *per; + const char *sunpriv; + + switch(family) { + case AF_INET: + per = "-4"; + break; + case AF_INET6: + per = "-6"; + break; + default: + per = ""; + break; + } + if (unpriv) + sunpriv = ifname ? ".unpriv" : "unpriv."; + else + sunpriv = ""; + return snprintf(path, len, CONTROLSOCKET, + ifname ? ifname : "", ifname ? per : "", + sunpriv, ifname ? "." : ""); +} + +static int +make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family, + bool unpriv) +{ + int fd; + + if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0)) == -1) + return -1; + memset(sa, 0, sizeof(*sa)); + sa->sun_family = AF_UNIX; + make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family, unpriv); + return fd; +} + +#define S_PRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) +#define S_UNPRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) + +static int +control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family, + mode_t fmode) +{ + struct sockaddr_un sa; + int fd; + socklen_t len; + + fd = make_sock(&sa, ifname, family, (fmode & S_UNPRIV) == S_UNPRIV); + if (fd == -1) + return -1; + + len = (socklen_t)SUN_LEN(&sa); + unlink(sa.sun_path); + if (bind(fd, (struct sockaddr *)&sa, len) == -1 || + chmod(sa.sun_path, fmode) == -1 || + (ctx->control_group && + chown(sa.sun_path, geteuid(), ctx->control_group) == -1) || + listen(fd, sizeof(ctx->control_fds)) == -1) + { + close(fd); + unlink(sa.sun_path); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if (IN_PRIVSEP(ctx) && ps_rights_limit_fd_fctnl(fd) == -1) { + close(fd); + unlink(sa.sun_path); + return -1; + } +#endif + + if ((fmode & S_UNPRIV) == S_UNPRIV) + strlcpy(ctx->control_sock_unpriv, sa.sun_path, + sizeof(ctx->control_sock_unpriv)); + else + strlcpy(ctx->control_sock, sa.sun_path, + sizeof(ctx->control_sock)); + return fd; +} + +int +control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) +{ + int fd; + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) { + make_path(ctx->control_sock, sizeof(ctx->control_sock), + ifname, family, false); + make_path(ctx->control_sock_unpriv, + sizeof(ctx->control_sock_unpriv), + ifname, family, true); + return 0; + } +#endif + + if ((fd = control_start1(ctx, ifname, family, S_PRIV)) == -1) + return -1; + + ctx->control_fd = fd; + eloop_event_add(ctx->eloop, fd, control_handle, ctx); + + if ((fd = control_start1(ctx, ifname, family, S_UNPRIV)) != -1) { + ctx->control_unpriv_fd = fd; + eloop_event_add(ctx->eloop, fd, control_handle_unpriv, ctx); + } + return ctx->control_fd; +} + +static int +control_unlink(struct dhcpcd_ctx *ctx, const char *file) +{ + int retval = 0; + + errno = 0; +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx)) + retval = (int)ps_root_unlink(ctx, file); + else +#else + UNUSED(ctx); +#endif + retval = unlink(file); + + return retval == -1 && errno != ENOENT ? -1 : 0; +} + +int +control_stop(struct dhcpcd_ctx *ctx) +{ + int retval = 0; + struct fd_list *l; + + while ((l = TAILQ_FIRST(&ctx->control_fds)) != NULL) { + control_free(l); + } + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) { + if (ps_root_unlink(ctx, ctx->control_sock) == -1) + retval = -1; + if (ps_root_unlink(ctx, ctx->control_sock_unpriv) == -1) + retval = -1; + return retval; + } else if (ctx->options & DHCPCD_FORKED) + return retval; +#endif + + if (ctx->control_fd != -1) { + eloop_event_delete(ctx->eloop, ctx->control_fd); + close(ctx->control_fd); + ctx->control_fd = -1; + if (control_unlink(ctx, ctx->control_sock) == -1) + retval = -1; + } + + if (ctx->control_unpriv_fd != -1) { + eloop_event_delete(ctx->eloop, ctx->control_unpriv_fd); + close(ctx->control_unpriv_fd); + ctx->control_unpriv_fd = -1; + if (control_unlink(ctx, ctx->control_sock_unpriv) == -1) + retval = -1; + } + + return retval; +} + +int +control_open(const char *ifname, sa_family_t family, bool unpriv) +{ + struct sockaddr_un sa; + int fd; + + if ((fd = make_sock(&sa, ifname, family, unpriv)) != -1) { + socklen_t len; + + len = (socklen_t)SUN_LEN(&sa); + if (connect(fd, (struct sockaddr *)&sa, len) == -1) { + close(fd); + fd = -1; + } + } + return fd; +} + +ssize_t +control_send(struct dhcpcd_ctx *ctx, int argc, char * const *argv) +{ + char buffer[1024]; + int i; + size_t len, l; + + if (argc > 255) { + errno = ENOBUFS; + return -1; + } + len = 0; + for (i = 0; i < argc; i++) { + l = strlen(argv[i]) + 1; + if (len + l > sizeof(buffer)) { + errno = ENOBUFS; + return -1; + } + memcpy(buffer + len, argv[i], l); + len += l; + } + return write(ctx->control_fd, buffer, len); +} + +static void +control_writeone(void *arg) +{ + struct fd_list *fd; + struct iovec iov[2]; + int iov_len; + struct fd_data *data; + + fd = arg; + data = TAILQ_FIRST(&fd->queue); + + if (data->data_flags & FD_SENDLEN) { + iov[0].iov_base = &data->data_len; + iov[0].iov_len = sizeof(size_t); + iov[1].iov_base = data->data; + iov[1].iov_len = data->data_len; + iov_len = 2; + } else { + iov[0].iov_base = data->data; + iov[0].iov_len = data->data_len; + iov_len = 1; + } + + if (writev(fd->fd, iov, iov_len) == -1) { + logerr("%s: write", __func__); + control_delete(fd); + return; + } + + TAILQ_REMOVE(&fd->queue, data, next); +#ifdef CTL_FREE_LIST + TAILQ_INSERT_TAIL(&fd->free_queue, data, next); +#else + if (data->data_size != 0) + free(data->data); + free(data); +#endif + + if (TAILQ_FIRST(&fd->queue) != NULL) + return; + + if (eloop_event_remove_writecb(fd->ctx->eloop, fd->fd) == -1) + logerr(__func__); +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(fd->ctx) && !(fd->flags & FD_LISTEN)) { + if (ps_ctl_sendeof(fd) == -1) + logerr(__func__); + control_free(fd); + } +#endif +} + +int +control_queue(struct fd_list *fd, void *data, size_t data_len) +{ + struct fd_data *d; + + if (data_len == 0) { + errno = EINVAL; + return -1; + } + +#ifdef CTL_FREE_LIST + struct fd_data *df; + + d = NULL; + TAILQ_FOREACH(df, &fd->free_queue, next) { + if (d == NULL || d->data_size < df->data_size) { + d = df; + if (d->data_size <= data_len) + break; + } + } + if (d != NULL) + TAILQ_REMOVE(&fd->free_queue, d, next); + else +#endif + { + d = calloc(1, sizeof(*d)); + if (d == NULL) + return -1; + } + + if (d->data_size == 0) + d->data = NULL; + if (d->data_size < data_len) { + void *nbuf = realloc(d->data, data_len); + if (nbuf == NULL) { + free(d->data); + free(d); + return -1; + } + d->data = nbuf; + d->data_size = data_len; + } + memcpy(d->data, data, data_len); + d->data_len = data_len; + d->data_flags = fd->flags & FD_SENDLEN; + + TAILQ_INSERT_TAIL(&fd->queue, d, next); + eloop_event_add_w(fd->ctx->eloop, fd->fd, control_writeone, fd); + return 0; +} diff --git a/src/control.h b/src/control.h new file mode 100644 index 000000000000..110b0a7b2f0b --- /dev/null +++ b/src/control.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#ifndef CONTROL_H +#define CONTROL_H + +#include <stdbool.h> + +#include "dhcpcd.h" + +#if !defined(CTL_FREE_LIST) +#define CTL_FREE_LIST 1 +#elif CTL_FREE_LIST == 0 +#undef CTL_FREE_LIST +#endif + +/* Limit queue size per fd */ +#define CONTROL_QUEUE_MAX 100 + +struct fd_data { + TAILQ_ENTRY(fd_data) next; + void *data; + size_t data_size; + size_t data_len; + unsigned int data_flags; +}; +TAILQ_HEAD(fd_data_head, fd_data); + +struct fd_list { + TAILQ_ENTRY(fd_list) next; + struct dhcpcd_ctx *ctx; + int fd; + unsigned int flags; + struct fd_data_head queue; +#ifdef CTL_FREE_LIST + struct fd_data_head free_queue; +#endif +}; +TAILQ_HEAD(fd_list_head, fd_list); + +#define FD_LISTEN 0x01U +#define FD_UNPRIV 0x02U +#define FD_SENDLEN 0x04U + +int control_start(struct dhcpcd_ctx *, const char *, sa_family_t); +int control_stop(struct dhcpcd_ctx *); +int control_open(const char *, sa_family_t, bool); +ssize_t control_send(struct dhcpcd_ctx *, int, char * const *); +struct fd_list *control_new(struct dhcpcd_ctx *, int, unsigned int); +void control_free(struct fd_list *); +void control_delete(struct fd_list *); +int control_queue(struct fd_list *, void *, size_t); +void control_recvdata(struct fd_list *fd, char *, size_t); +#endif diff --git a/src/defs.h b/src/defs.h new file mode 100644 index 000000000000..cde76f8857b0 --- /dev/null +++ b/src/defs.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 Roy Marples <roy@marples.name> + * + * 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. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#define PACKAGE "dhcpcd" +#define VERSION "9.4.1" + +#ifndef PRIVSEP_USER +# define PRIVSEP_USER "_" PACKAGE +#endif + +#ifndef CONFIG +# define CONFIG SYSCONFDIR "/" PACKAGE ".conf" +#endif +#ifndef SCRIPT +# define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks" +#endif +#ifndef DEVDIR +# define DEVDIR LIBDIR "/" PACKAGE "/dev" +#endif +#ifndef DUID +# define DUID DBDIR "/duid" +#endif +#ifndef SECRET +# define SECRET DBDIR "/secret" +#endif +#ifndef LEASEFILE +# define LEASEFILE DBDIR "/%s%s.lease" +#endif +#ifndef LEASEFILE6 +# define LEASEFILE6 LEASEFILE "6" +#endif +#ifndef PIDFILE +# define PIDFILE RUNDIR "/%s%s%spid" +#endif +#ifndef CONTROLSOCKET +# define CONTROLSOCKET RUNDIR "/%s%s%s%ssock" +#endif +#ifndef RDM_MONOFILE +# define RDM_MONOFILE DBDIR "/rdm_monotonic" +#endif + +#ifndef NO_SIGNALS +# define USE_SIGNALS +#endif +#ifndef USE_SIGNALS +# ifndef THERE_IS_NO_FORK +# define THERE_IS_NO_FORK +# endif +#endif + +#endif diff --git a/src/dev.c b/src/dev.c new file mode 100644 index 000000000000..d33c8faaa230 --- /dev/null +++ b/src/dev.c @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dirent.h> +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define _INDEV +#include "common.h" +#include "dev.h" +#include "eloop.h" +#include "dhcpcd.h" +#include "logerr.h" + +int +dev_initialised(struct dhcpcd_ctx *ctx, const char *ifname) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return ps_root_dev_initialised(ctx, ifname); +#endif + + if (ctx->dev == NULL) + return 1; + return ctx->dev->initialised(ifname); +} + +int +dev_listening(struct dhcpcd_ctx *ctx) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return ps_root_dev_listening(ctx); +#endif + + if (ctx->dev == NULL) + return 0; + return ctx->dev->listening(); +} + +static void +dev_stop1(struct dhcpcd_ctx *ctx, int stop) +{ + + if (ctx->dev) { + if (stop) + logdebugx("dev: unloaded %s", ctx->dev->name); + eloop_event_delete(ctx->eloop, ctx->dev_fd); + ctx->dev->stop(); + free(ctx->dev); + ctx->dev = NULL; + ctx->dev_fd = -1; + } + if (ctx->dev_handle) { + dlclose(ctx->dev_handle); + ctx->dev_handle = NULL; + } +} + +void +dev_stop(struct dhcpcd_ctx *ctx) +{ + + dev_stop1(ctx, !(ctx->options & DHCPCD_FORKED)); +} + +static int +dev_start2(struct dhcpcd_ctx *ctx, const struct dev_dhcpcd *dev_dhcpcd, + const char *name) +{ + char file[PATH_MAX]; + void *h; + void (*fptr)(struct dev *, const struct dev_dhcpcd *); + int r; + + snprintf(file, sizeof(file), DEVDIR "/%s", name); + h = dlopen(file, RTLD_LAZY); + if (h == NULL) { + logerrx("dlopen: %s", dlerror()); + return -1; + } + fptr = dlsym(h, "dev_init"); + if (fptr == NULL) { + logerrx("dlsym: %s", dlerror()); + dlclose(h); + return -1; + } + if ((ctx->dev = calloc(1, sizeof(*ctx->dev))) == NULL) { + logerr("%s: calloc", __func__); + dlclose(h); + return -1; + } + fptr(ctx->dev, dev_dhcpcd); + if (ctx->dev->start == NULL || (r = ctx->dev->start()) == -1) { + free(ctx->dev); + ctx->dev = NULL; + dlclose(h); + return -1; + } + loginfox("dev: loaded %s", ctx->dev->name); + ctx->dev_handle = h; + return r; +} + +static int +dev_start1(struct dhcpcd_ctx *ctx, const struct dev_dhcpcd *dev_dhcpcd) +{ + DIR *dp; + struct dirent *d; + int r; + + if (ctx->dev) { + logerrx("dev: already started %s", ctx->dev->name); + return -1; + } + + if (ctx->dev_load) + return dev_start2(ctx, dev_dhcpcd, ctx->dev_load); + + dp = opendir(DEVDIR); + if (dp == NULL) { + logdebug("dev: %s", DEVDIR); + return 0; + } + + r = 0; + while ((d = readdir(dp))) { + if (d->d_name[0] == '.') + continue; + + r = dev_start2(ctx, dev_dhcpcd, d->d_name); + if (r != -1) + break; + } + closedir(dp); + return r; +} + +static void +dev_handle_data(void *arg) +{ + struct dhcpcd_ctx *ctx; + + ctx = arg; + if (ctx->dev->handle_device(arg) == -1) { + /* XXX: an error occured. should we restart dev? */ + } +} + +int +dev_start(struct dhcpcd_ctx *ctx, int (*handler)(void *, int, const char *)) +{ + struct dev_dhcpcd dev_dhcpcd = { + .handle_interface = handler, + }; + + if (ctx->dev_fd != -1) { + logerrx("%s: already started on fd %d", __func__, ctx->dev_fd); + return ctx->dev_fd; + } + + ctx->dev_fd = dev_start1(ctx, &dev_dhcpcd); + if (ctx->dev_fd != -1) { + if (eloop_event_add(ctx->eloop, ctx->dev_fd, + dev_handle_data, ctx) == -1) + { + logerr(__func__); + dev_stop1(ctx, 1); + return -1; + } + } + + return ctx->dev_fd; +} diff --git a/src/dev.h b/src/dev.h new file mode 100644 index 000000000000..e7263c5b87da --- /dev/null +++ b/src/dev.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 Roy Marples <roy@marples.name> + * + * 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. + */ + +#ifndef DEV_H +#define DEV_H + +// dev plugin setup +struct dev { + const char *name; + int (*initialised)(const char *); + int (*listening)(void); + int (*handle_device)(void *); + int (*start)(void); + void (*stop)(void); +}; + +struct dev_dhcpcd { + int (*handle_interface)(void *, int, const char *); +}; + +int dev_init(struct dev *, const struct dev_dhcpcd *); + +// hooks for dhcpcd +#ifdef PLUGIN_DEV +#include "dhcpcd.h" +int dev_initialised(struct dhcpcd_ctx *, const char *); +int dev_listening(struct dhcpcd_ctx *); +int dev_start(struct dhcpcd_ctx *, int (*)(void *, int, const char *)); +void dev_stop(struct dhcpcd_ctx *); +#endif + +#endif diff --git a/src/dev/Makefile b/src/dev/Makefile new file mode 100644 index 000000000000..7961149f390d --- /dev/null +++ b/src/dev/Makefile @@ -0,0 +1,45 @@ +TOP= ../../ +include ${TOP}/Makefile.inc +include ${TOP}/config.mk + +CFLAGS?= -O2 +CSTD?= c99 +CFLAGS+= -std=${CSTD} +CPPFLAGS+= -I${TOP} -I${TOP}/src + +DEVDIR= ${LIBDIR}/dhcpcd/dev +DSRC= ${DEV_PLUGINS:=.c} +DOBJ= ${DSRC:.c=.o} +DSOBJ= ${DOBJ:.o=.So} +DPLUGS= ${DEV_PLUGINS:=.so} + +CLEANFILES+= ${DSOBJ} ${DPLUGS} + +.SUFFIXES: .So .so + +.c.So: + ${CC} ${PICFLAG} -DPIC ${CPPFLAGS} ${CFLAGS} -c $< -o $@ + +.So.so: ${DSOBJ} + ${CC} ${LDFLAGS} -shared -Wl,-x -o $@ -Wl,-soname,$@ \ + $< ${LIBS} + +all: ${DPLUGS} + +udev.So: +CFLAGS+= ${LIBUDEV_CFLAGS} +CPPFLAGS+= ${LIBUDEV_CPPFLAGS} + +udev.so: +LIBS+= ${LIBUDEV_LIBS} + +proginstall: ${DPLUGS} + ${INSTALL} -d ${DESTDIR}${DEVDIR} + ${INSTALL} -m ${BINMODE} ${PROG} ${DPLUGS} ${DESTDIR}${DEVDIR} + +eginstall: + +install: proginstall + +clean: + rm -f ${CLEANFILES} diff --git a/src/dev/udev.c b/src/dev/udev.c new file mode 100644 index 000000000000..552f37bc8505 --- /dev/null +++ b/src/dev/udev.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 Roy Marples <roy@marples.name> + * + * 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. + */ + +#ifdef LIBUDEV_NOINIT +# define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE +# warning This version of udev is too old does not support +# warning per device initialization checks. +# warning As such, dhcpcd will need to depend on the +# warning udev-settle service or similar if starting +# warning in master mode. +#endif + +#include <libudev.h> +#include <string.h> + +#include "../common.h" +#include "../dev.h" +#include "../if.h" +#include "../logerr.h" + +static const char udev_name[] = "udev"; +static struct udev *udev; +static struct udev_monitor *monitor; + +static struct dev_dhcpcd dhcpcd; + +static int +udev_listening(void) +{ + + return monitor ? 1 : 0; +} + +static int +udev_initialised(const char *ifname) +{ + struct udev_device *device; + int r; + + device = udev_device_new_from_subsystem_sysname(udev, "net", ifname); + if (device) { +#ifndef LIBUDEV_NOINIT + r = udev_device_get_is_initialized(device); +#else + r = 1; +#endif + udev_device_unref(device); + } else + r = 0; + return r; +} + +static int +udev_handle_device(void *ctx) +{ + struct udev_device *device; + const char *subsystem, *ifname, *action; + + device = udev_monitor_receive_device(monitor); + if (device == NULL) { + logerrx("libudev: received NULL device"); + return -1; + } + + subsystem = udev_device_get_subsystem(device); + ifname = udev_device_get_sysname(device); + action = udev_device_get_action(device); + + /* udev filter documentation says "usually" so double check */ + if (strcmp(subsystem, "net") == 0) { + logdebugx("%s: libudev: %s", ifname, action); + if (strcmp(action, "add") == 0 || strcmp(action, "move") == 0) + dhcpcd.handle_interface(ctx, 1, ifname); + else if (strcmp(action, "remove") == 0) + dhcpcd.handle_interface(ctx, -1, ifname); + } + + udev_device_unref(device); + return 1; +} + +static void +udev_stop(void) +{ + + if (monitor) { + udev_monitor_unref(monitor); + monitor = NULL; + } + + if (udev) { + udev_unref(udev); + udev = NULL; + } +} + +static int +udev_start(void) +{ + char netns[PATH_MAX]; + int fd; + + if (if_getnetworknamespace(netns, sizeof(netns)) != NULL) { + logdebugx("udev does not work in a network namespace"); + return -1; + } + + if (udev) { + logerrx("udev: already started"); + return -1; + } + + logdebugx("udev: starting"); + udev = udev_new(); + if (udev == NULL) { + logerr("udev_new"); + return -1; + } + monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (monitor == NULL) { + logerr("udev_monitor_new_from_netlink"); + goto bad; + } +#ifndef LIBUDEV_NOFILTER + if (udev_monitor_filter_add_match_subsystem_devtype(monitor, + "net", NULL) != 0) + { + logerr("udev_monitor_filter_add_match_subsystem_devtype"); + goto bad; + } +#endif + if (udev_monitor_enable_receiving(monitor) != 0) { + logerr("udev_monitor_enable_receiving"); + goto bad; + } + fd = udev_monitor_get_fd(monitor); + if (fd == -1) { + logerr("udev_monitor_get_fd"); + goto bad; + } + return fd; + +bad: + udev_stop(); + return -1; +} + +int +dev_init(struct dev *dev, const struct dev_dhcpcd *dev_dhcpcd) +{ + + dev->name = udev_name; + dev->initialised = udev_initialised; + dev->listening = udev_listening; + dev->handle_device = udev_handle_device; + dev->stop = udev_stop; + dev->start = udev_start; + + dhcpcd = *dev_dhcpcd; + + return 0; +} diff --git a/src/dhcp-common.c b/src/dhcp-common.c new file mode 100644 index 000000000000..dbcfcc564df8 --- /dev/null +++ b/src/dhcp-common.c @@ -0,0 +1,1029 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#include <sys/utsname.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" + +#include "common.h" +#include "dhcp-common.h" +#include "dhcp.h" +#include "if.h" +#include "ipv6.h" +#include "logerr.h" +#include "script.h" + +const char * +dhcp_get_hostname(char *buf, size_t buf_len, const struct if_options *ifo) +{ + + if (ifo->hostname[0] == '\0') { + if (gethostname(buf, buf_len) != 0) + return NULL; + buf[buf_len - 1] = '\0'; + } else + strlcpy(buf, ifo->hostname, buf_len); + + /* Deny sending of these local hostnames */ + if (buf[0] == '\0' || buf[0] == '.' || + strcmp(buf, "(none)") == 0 || + strcmp(buf, "localhost") == 0 || + strncmp(buf, "localhost.", strlen("localhost.")) == 0) + return NULL; + + /* Shorten the hostname if required */ + if (ifo->options & DHCPCD_HOSTNAME_SHORT) { + char *hp; + + hp = strchr(buf, '.'); + if (hp != NULL) + *hp = '\0'; + } + + return buf; +} + +void +dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols) +{ + + while (cols < 40) { + putchar(' '); + cols++; + } + putchar('\t'); + if (opt->type & OT_EMBED) + printf(" embed"); + if (opt->type & OT_ENCAP) + printf(" encap"); + if (opt->type & OT_INDEX) + printf(" index"); + if (opt->type & OT_ARRAY) + printf(" array"); + if (opt->type & OT_UINT8) + printf(" uint8"); + else if (opt->type & OT_INT8) + printf(" int8"); + else if (opt->type & OT_UINT16) + printf(" uint16"); + else if (opt->type & OT_INT16) + printf(" int16"); + else if (opt->type & OT_UINT32) + printf(" uint32"); + else if (opt->type & OT_INT32) + printf(" int32"); + else if (opt->type & OT_ADDRIPV4) + printf(" ipaddress"); + else if (opt->type & OT_ADDRIPV6) + printf(" ip6address"); + else if (opt->type & OT_FLAG) + printf(" flag"); + else if (opt->type & OT_BITFLAG) + printf(" bitflags"); + else if (opt->type & OT_RFC1035) + printf(" domain"); + else if (opt->type & OT_DOMAIN) + printf(" dname"); + else if (opt->type & OT_ASCII) + printf(" ascii"); + else if (opt->type & OT_RAW) + printf(" raw"); + else if (opt->type & OT_BINHEX) + printf(" binhex"); + else if (opt->type & OT_STRING) + printf(" string"); + if (opt->type & OT_RFC3361) + printf(" rfc3361"); + if (opt->type & OT_RFC3442) + printf(" rfc3442"); + if (opt->type & OT_REQUEST) + printf(" request"); + if (opt->type & OT_NOREQ) + printf(" norequest"); + putchar('\n'); +} + +struct dhcp_opt * +vivso_find(uint32_t iana_en, const void *arg) +{ + const struct interface *ifp; + size_t i; + struct dhcp_opt *opt; + + ifp = arg; + for (i = 0, opt = ifp->options->vivso_override; + i < ifp->options->vivso_override_len; + i++, opt++) + if (opt->option == iana_en) + return opt; + for (i = 0, opt = ifp->ctx->vivso; + i < ifp->ctx->vivso_len; + i++, opt++) + if (opt->option == iana_en) + return opt; + return NULL; +} + +ssize_t +dhcp_vendor(char *str, size_t len) +{ + struct utsname utn; + char *p; + int l; + + if (uname(&utn) == -1) + return (ssize_t)snprintf(str, len, "%s-%s", + PACKAGE, VERSION); + p = str; + l = snprintf(p, len, + "%s-%s:%s-%s:%s", PACKAGE, VERSION, + utn.sysname, utn.release, utn.machine); + if (l == -1 || (size_t)(l + 1) > len) + return -1; + p += l; + len -= (size_t)l; + l = if_machinearch(p + 1, len - 1); + if (l == -1 || (size_t)(l + 1) > len) + return -1; + *p = ':'; + p += l; + return p - str; +} + +int +make_option_mask(const struct dhcp_opt *dopts, size_t dopts_len, + const struct dhcp_opt *odopts, size_t odopts_len, + uint8_t *mask, const char *opts, int add) +{ + char *token, *o, *p; + const struct dhcp_opt *opt; + int match, e; + unsigned int n; + size_t i; + + if (opts == NULL) + return -1; + o = p = strdup(opts); + while ((token = strsep(&p, ", "))) { + if (*token == '\0') + continue; + if (strncmp(token, "dhcp6_", 6) == 0) + token += 6; + if (strncmp(token, "nd_", 3) == 0) + token += 3; + match = 0; + for (i = 0, opt = odopts; i < odopts_len; i++, opt++) { + if (opt->var == NULL || opt->option == 0) + continue; /* buggy dhcpcd-definitions.conf */ + if (strcmp(opt->var, token) == 0) + match = 1; + else { + n = (unsigned int)strtou(token, NULL, 0, + 0, UINT_MAX, &e); + if (e == 0 && opt->option == n) + match = 1; + } + if (match) + break; + } + if (match == 0) { + for (i = 0, opt = dopts; i < dopts_len; i++, opt++) { + if (strcmp(opt->var, token) == 0) + match = 1; + else { + n = (unsigned int)strtou(token, NULL, 0, + 0, UINT_MAX, &e); + if (e == 0 && opt->option == n) + match = 1; + } + if (match) + break; + } + } + if (!match || !opt->option) { + free(o); + errno = ENOENT; + return -1; + } + if (add == 2 && !(opt->type & OT_ADDRIPV4)) { + free(o); + errno = EINVAL; + return -1; + } + if (add == 1 || add == 2) + add_option_mask(mask, opt->option); + else + del_option_mask(mask, opt->option); + } + free(o); + return 0; +} + +size_t +encode_rfc1035(const char *src, uint8_t *dst) +{ + uint8_t *p; + uint8_t *lp; + size_t len; + uint8_t has_dot; + + if (src == NULL || *src == '\0') + return 0; + + if (dst) { + p = dst; + lp = p++; + } + /* Silence bogus GCC warnings */ + else + p = lp = NULL; + + len = 1; + has_dot = 0; + for (; *src; src++) { + if (*src == '\0') + break; + if (*src == '.') { + /* Skip the trailing . */ + if (src[1] == '\0') + break; + has_dot = 1; + if (dst) { + *lp = (uint8_t)(p - lp - 1); + if (*lp == '\0') + return len; + lp = p++; + } + } else if (dst) + *p++ = (uint8_t)*src; + len++; + } + + if (dst) { + *lp = (uint8_t)(p - lp - 1); + if (has_dot) + *p++ = '\0'; + } + + if (has_dot) + len++; + + return len; +} + +/* Decode an RFC1035 DNS search order option into a space + * separated string. Returns length of string (including + * terminating zero) or zero on error. out may be NULL + * to just determine output length. */ +ssize_t +decode_rfc1035(char *out, size_t len, const uint8_t *p, size_t pl) +{ + const char *start; + size_t start_len, l, d_len, o_len; + const uint8_t *r, *q = p, *e; + int hops; + uint8_t ltype; + + o_len = 0; + start = out; + start_len = len; + q = p; + e = p + pl; + while (q < e) { + r = NULL; + d_len = 0; + hops = 0; + /* Check we are inside our length again in-case + * the name isn't fully qualified (ie, not terminated) */ + while (q < e && (l = (size_t)*q++)) { + ltype = l & 0xc0; + if (ltype == 0x80 || ltype == 0x40) { + /* Currently reserved for future use as noted + * in RFC1035 4.1.4 as the 10 and 01 + * combinations. */ + errno = ENOTSUP; + return -1; + } + else if (ltype == 0xc0) { /* pointer */ + if (q == e) { + errno = ERANGE; + return -1; + } + l = (l & 0x3f) << 8; + l |= *q++; + /* save source of first jump. */ + if (!r) + r = q; + hops++; + if (hops > 255) { + errno = ERANGE; + return -1; + } + q = p + l; + if (q >= e) { + errno = ERANGE; + return -1; + } + } else { + /* straightforward name segment, add with '.' */ + if (q + l > e) { + errno = ERANGE; + return -1; + } + if (l > NS_MAXLABEL) { + errno = EINVAL; + return -1; + } + d_len += l + 1; + if (out) { + if (l + 1 > len) { + errno = ENOBUFS; + return -1; + } + memcpy(out, q, l); + out += l; + *out++ = '.'; + len -= l; + len--; + } + q += l; + } + } + + /* Don't count the trailing NUL */ + if (d_len > NS_MAXDNAME + 1) { + errno = E2BIG; + return -1; + } + o_len += d_len; + + /* change last dot to space */ + if (out && out != start) + *(out - 1) = ' '; + if (r) + q = r; + } + + /* change last space to zero terminator */ + if (out) { + if (out != start) + *(out - 1) = '\0'; + else if (start_len > 0) + *out = '\0'; + } + + /* Remove the trailing NUL */ + if (o_len != 0) + o_len--; + + return (ssize_t)o_len; +} + +/* Check for a valid name as per RFC952 and RFC1123 section 2.1 */ +static int +valid_domainname(char *lbl, int type) +{ + char *slbl, *lst; + unsigned char c; + int start, len, errset; + + if (lbl == NULL || *lbl == '\0') { + errno = EINVAL; + return 0; + } + + slbl = lbl; + lst = NULL; + start = 1; + len = errset = 0; + for (;;) { + c = (unsigned char)*lbl++; + if (c == '\0') + return 1; + if (c == ' ') { + if (lbl - 1 == slbl) /* No space at start */ + break; + if (!(type & OT_ARRAY)) + break; + /* Skip to the next label */ + if (!start) { + start = 1; + lst = lbl - 1; + } + if (len) + len = 0; + continue; + } + if (c == '.') { + if (*lbl == '.') + break; + len = 0; + continue; + } + if (((c == '-' || c == '_') && + !start && *lbl != ' ' && *lbl != '\0') || + isalnum(c)) + { + if (++len > NS_MAXLABEL) { + errno = ERANGE; + errset = 1; + break; + } + } else + break; + if (start) + start = 0; + } + + if (!errset) + errno = EINVAL; + if (lst) { + /* At least one valid domain, return it */ + *lst = '\0'; + return 1; + } + return 0; +} + +/* + * Prints a chunk of data to a string. + * PS_SHELL goes as it is these days, it's upto the target to validate it. + * PS_SAFE has all non ascii and non printables changes to escaped octal. + */ +static const char hexchrs[] = "0123456789abcdef"; +ssize_t +print_string(char *dst, size_t len, int type, const uint8_t *data, size_t dl) +{ + char *odst; + uint8_t c; + const uint8_t *e; + size_t bytes; + + odst = dst; + bytes = 0; + e = data + dl; + + while (data < e) { + c = *data++; + if (type & OT_BINHEX) { + if (dst) { + if (len == 0 || len == 1) { + errno = ENOBUFS; + return -1; + } + *dst++ = hexchrs[(c & 0xF0) >> 4]; + *dst++ = hexchrs[(c & 0x0F)]; + len -= 2; + } + bytes += 2; + continue; + } + if (type & OT_ASCII && (!isascii(c))) { + errno = EINVAL; + break; + } + if (!(type & (OT_ASCII | OT_RAW | OT_ESCSTRING | OT_ESCFILE)) && + (!isascii(c) && !isprint(c))) + { + errno = EINVAL; + break; + } + if ((type & (OT_ESCSTRING | OT_ESCFILE) && + (c == '\\' || !isascii(c) || !isprint(c))) || + (type & OT_ESCFILE && (c == '/' || c == ' '))) + { + errno = EINVAL; + if (c == '\\') { + if (dst) { + if (len == 0 || len == 1) { + errno = ENOBUFS; + return -1; + } + *dst++ = '\\'; *dst++ = '\\'; + len -= 2; + } + bytes += 2; + continue; + } + if (dst) { + if (len < 5) { + errno = ENOBUFS; + return -1; + } + *dst++ = '\\'; + *dst++ = (char)(((c >> 6) & 03) + '0'); + *dst++ = (char)(((c >> 3) & 07) + '0'); + *dst++ = (char)(( c & 07) + '0'); + len -= 4; + } + bytes += 4; + } else { + if (dst) { + if (len == 0) { + errno = ENOBUFS; + return -1; + } + *dst++ = (char)c; + len--; + } + bytes++; + } + } + + /* NULL */ + if (dst) { + if (len == 0) { + errno = ENOBUFS; + return -1; + } + *dst = '\0'; + + /* Now we've printed it, validate the domain */ + if (type & OT_DOMAIN && !valid_domainname(odst, type)) { + *odst = '\0'; + return 1; + } + + } + + return (ssize_t)bytes; +} + +#define ADDR6SZ 16 +static ssize_t +dhcp_optlen(const struct dhcp_opt *opt, size_t dl) +{ + size_t sz; + + if (opt->type & OT_ADDRIPV6) + sz = ADDR6SZ; + else if (opt->type & (OT_INT32 | OT_UINT32 | OT_ADDRIPV4)) + sz = sizeof(uint32_t); + else if (opt->type & (OT_INT16 | OT_UINT16)) + sz = sizeof(uint16_t); + else if (opt->type & (OT_INT8 | OT_UINT8 | OT_BITFLAG)) + sz = sizeof(uint8_t); + else if (opt->type & OT_FLAG) + return 0; + else { + /* All other types are variable length */ + if (opt->len) { + if ((size_t)opt->len > dl) { + errno = EOVERFLOW; + return -1; + } + return (ssize_t)opt->len; + } + return (ssize_t)dl; + } + if (dl < sz) { + errno = EOVERFLOW; + return -1; + } + + /* Trim any extra data. + * Maybe we need a setting to reject DHCP options with extra data? */ + if (opt->type & OT_ARRAY) + return (ssize_t)(dl - (dl % sz)); + return (ssize_t)sz; +} + +static ssize_t +print_option(FILE *fp, const char *prefix, const struct dhcp_opt *opt, + int vname, + const uint8_t *data, size_t dl, const char *ifname) +{ + fpos_t fp_pos; + const uint8_t *e, *t; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + struct in_addr addr; + ssize_t sl; + size_t l; + + /* Ensure a valid length */ + dl = (size_t)dhcp_optlen(opt, dl); + if ((ssize_t)dl == -1) + return 0; + + if (fgetpos(fp, &fp_pos) == -1) + return -1; + if (fprintf(fp, "%s", prefix) == -1) + goto err; + + /* We printed something, so always goto err from now-on + * to terminate the string. */ + if (vname) { + if (fprintf(fp, "_%s", opt->var) == -1) + goto err; + } + if (fputc('=', fp) == EOF) + goto err; + if (dl == 0) + goto done; + + if (opt->type & OT_RFC1035) { + char domain[NS_MAXDNAME]; + + sl = decode_rfc1035(domain, sizeof(domain), data, dl); + if (sl == -1) + goto err; + if (sl == 0) + goto done; + if (valid_domainname(domain, opt->type) == -1) + goto err; + return efprintf(fp, "%s", domain); + } + +#ifdef INET + if (opt->type & OT_RFC3361) + return print_rfc3361(fp, data, dl); + + if (opt->type & OT_RFC3442) + return print_rfc3442(fp, data, dl); +#endif + + if (opt->type & OT_STRING) { + char buf[1024]; + + if (print_string(buf, sizeof(buf), opt->type, data, dl) == -1) + goto err; + return efprintf(fp, "%s", buf); + } + + if (opt->type & OT_FLAG) + return efprintf(fp, "1"); + + if (opt->type & OT_BITFLAG) { + /* bitflags are a string, MSB first, such as ABCDEFGH + * where A is 10000000, B is 01000000, etc. */ + for (l = 0, sl = sizeof(opt->bitflags) - 1; + l < sizeof(opt->bitflags); + l++, sl--) + { + /* Don't print NULL or 0 flags */ + if (opt->bitflags[l] != '\0' && + opt->bitflags[l] != '0' && + *data & (1 << sl)) + { + if (fputc(opt->bitflags[l], fp) == EOF) + goto err; + } + } + goto done; + } + + t = data; + e = data + dl; + while (data < e) { + if (data != t) { + if (fputc(' ', fp) == EOF) + goto err; + } + if (opt->type & OT_UINT8) { + if (fprintf(fp, "%u", *data) == -1) + goto err; + data++; + } else if (opt->type & OT_INT8) { + if (fprintf(fp, "%d", *data) == -1) + goto err; + data++; + } else if (opt->type & OT_UINT16) { + memcpy(&u16, data, sizeof(u16)); + u16 = ntohs(u16); + if (fprintf(fp, "%u", u16) == -1) + goto err; + data += sizeof(u16); + } else if (opt->type & OT_INT16) { + memcpy(&u16, data, sizeof(u16)); + s16 = (int16_t)ntohs(u16); + if (fprintf(fp, "%d", s16) == -1) + goto err; + data += sizeof(u16); + } else if (opt->type & OT_UINT32) { + memcpy(&u32, data, sizeof(u32)); + u32 = ntohl(u32); + if (fprintf(fp, "%u", u32) == -1) + goto err; + data += sizeof(u32); + } else if (opt->type & OT_INT32) { + memcpy(&u32, data, sizeof(u32)); + s32 = (int32_t)ntohl(u32); + if (fprintf(fp, "%d", s32) == -1) + goto err; + data += sizeof(u32); + } else if (opt->type & OT_ADDRIPV4) { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + if (fprintf(fp, "%s", inet_ntoa(addr)) == -1) + goto err; + data += sizeof(addr.s_addr); + } else if (opt->type & OT_ADDRIPV6) { + char buf[INET6_ADDRSTRLEN]; + + if (inet_ntop(AF_INET6, data, buf, sizeof(buf)) == NULL) + goto err; + if (fprintf(fp, "%s", buf) == -1) + goto err; + if (data[0] == 0xfe && (data[1] & 0xc0) == 0x80) { + if (fprintf(fp,"%%%s", ifname) == -1) + goto err; + } + data += 16; + } else { + errno = EINVAL; + goto err; + } + } + +done: + if (fputc('\0', fp) == EOF) + return -1; + return 1; + +err: + (void)fsetpos(fp, &fp_pos); + return -1; +} + +int +dhcp_set_leasefile(char *leasefile, size_t len, int family, + const struct interface *ifp) +{ + char ssid[1 + (IF_SSIDLEN * 4) + 1]; /* - prefix and NUL terminated. */ + + if (ifp->name[0] == '\0') { + strlcpy(leasefile, ifp->ctx->pidfile, len); + return 0; + } + + switch (family) { + case AF_INET: + case AF_INET6: + break; + default: + errno = EINVAL; + return -1; + } + + if (ifp->wireless) { + ssid[0] = '-'; + print_string(ssid + 1, sizeof(ssid) - 1, + OT_ESCFILE, + (const uint8_t *)ifp->ssid, ifp->ssid_len); + } else + ssid[0] = '\0'; + return snprintf(leasefile, len, + family == AF_INET ? LEASEFILE : LEASEFILE6, + ifp->name, ssid); +} + +void +dhcp_envoption(struct dhcpcd_ctx *ctx, FILE *fp, const char *prefix, + const char *ifname, struct dhcp_opt *opt, + const uint8_t *(*dgetopt)(struct dhcpcd_ctx *, + size_t *, unsigned int *, size_t *, + const uint8_t *, size_t, struct dhcp_opt **), + const uint8_t *od, size_t ol) +{ + size_t i, eos, eol; + ssize_t eo; + unsigned int eoc; + const uint8_t *eod; + int ov; + struct dhcp_opt *eopt, *oopt; + char *pfx; + + /* If no embedded or encapsulated options, it's easy */ + if (opt->embopts_len == 0 && opt->encopts_len == 0) { + if (opt->type & OT_RESERVED) + return; + if (print_option(fp, prefix, opt, 1, od, ol, ifname) == -1) + logerr("%s: %s %d", ifname, __func__, opt->option); + return; + } + + /* Create a new prefix based on the option */ + if (opt->type & OT_INDEX) { + if (asprintf(&pfx, "%s_%s%d", + prefix, opt->var, ++opt->index) == -1) + pfx = NULL; + } else { + if (asprintf(&pfx, "%s_%s", prefix, opt->var) == -1) + pfx = NULL; + } + if (pfx == NULL) { + logerr(__func__); + return; + } + + /* Embedded options are always processed first as that + * is a fixed layout */ + for (i = 0, eopt = opt->embopts; i < opt->embopts_len; i++, eopt++) { + eo = dhcp_optlen(eopt, ol); + if (eo == -1) { + logerrx("%s: %s %d.%d/%zu: " + "malformed embedded option", + ifname, __func__, opt->option, + eopt->option, i); + goto out; + } + if (eo == 0) { + /* An option was expected, but there is no data + * data for it. + * This may not be an error as some options like + * DHCP FQDN in RFC4702 have a string as the last + * option which is optional. */ + if (ol != 0 || !(eopt->type & OT_OPTIONAL)) + logerrx("%s: %s %d.%d/%zu: " + "missing embedded option", + ifname, __func__, opt->option, + eopt->option, i); + goto out; + } + /* Use the option prefix if the embedded option + * name is different. + * This avoids new_fqdn_fqdn which would be silly. */ + if (!(eopt->type & OT_RESERVED)) { + ov = strcmp(opt->var, eopt->var); + if (print_option(fp, pfx, eopt, ov, od, (size_t)eo, + ifname) == -1) + logerr("%s: %s %d.%d/%zu", + ifname, __func__, + opt->option, eopt->option, i); + } + od += (size_t)eo; + ol -= (size_t)eo; + } + + /* Enumerate our encapsulated options */ + if (opt->encopts_len && ol > 0) { + /* Zero any option indexes + * We assume that referenced encapsulated options are NEVER + * recursive as the index order could break. */ + for (i = 0, eopt = opt->encopts; + i < opt->encopts_len; + i++, eopt++) + { + eoc = opt->option; + if (eopt->type & OT_OPTION) { + dgetopt(ctx, NULL, &eoc, NULL, NULL, 0, &oopt); + if (oopt) + oopt->index = 0; + } + } + + while ((eod = dgetopt(ctx, &eos, &eoc, &eol, od, ol, &oopt))) { + for (i = 0, eopt = opt->encopts; + i < opt->encopts_len; + i++, eopt++) + { + if (eopt->option != eoc) + continue; + if (eopt->type & OT_OPTION) { + if (oopt == NULL) + /* Report error? */ + continue; + } + dhcp_envoption(ctx, fp, pfx, ifname, + eopt->type & OT_OPTION ? oopt:eopt, + dgetopt, eod, eol); + } + od += eos + eol; + ol -= eos + eol; + } + } + +out: + free(pfx); +} + +void +dhcp_zero_index(struct dhcp_opt *opt) +{ + size_t i; + struct dhcp_opt *o; + + opt->index = 0; + for (i = 0, o = opt->embopts; i < opt->embopts_len; i++, o++) + dhcp_zero_index(o); + for (i = 0, o = opt->encopts; i < opt->encopts_len; i++, o++) + dhcp_zero_index(o); +} + +ssize_t +dhcp_readfile(struct dhcpcd_ctx *ctx, const char *file, void *data, size_t len) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return ps_root_readfile(ctx, file, data, len); +#else + UNUSED(ctx); +#endif + + return readfile(file, data, len); +} + +ssize_t +dhcp_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode, + const void *data, size_t len) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return ps_root_writefile(ctx, file, mode, data, len); +#else + UNUSED(ctx); +#endif + + return writefile(file, mode, data, len); +} + +int +dhcp_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return (int)ps_root_filemtime(ctx, file, time); +#else + UNUSED(ctx); +#endif + + return filemtime(file, time); +} + +int +dhcp_unlink(struct dhcpcd_ctx *ctx, const char *file) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return (int)ps_root_unlink(ctx, file); +#else + UNUSED(ctx); +#endif + + return unlink(file); +} + +size_t +dhcp_read_hwaddr_aton(struct dhcpcd_ctx *ctx, uint8_t **data, const char *file) +{ + char buf[BUFSIZ]; + ssize_t bytes; + size_t len; + + bytes = dhcp_readfile(ctx, file, buf, sizeof(buf)); + if (bytes == -1 || bytes == sizeof(buf)) + return 0; + + bytes[buf] = '\0'; + len = hwaddr_aton(NULL, buf); + if (len == 0) + return 0; + *data = malloc(len); + if (*data == NULL) + return 0; + hwaddr_aton(*data, buf); + return len; +} diff --git a/src/dhcp-common.h b/src/dhcp-common.h new file mode 100644 index 000000000000..a82fcd4cec8e --- /dev/null +++ b/src/dhcp-common.h @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#ifndef DHCPCOMMON_H +#define DHCPCOMMON_H + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <stdint.h> + +#include <arpa/nameser.h> /* after normal includes for sunos */ + +#include "common.h" +#include "dhcpcd.h" + +/* Support very old arpa/nameser.h as found in OpenBSD */ +#ifndef NS_MAXDNAME +#define NS_MAXCDNAME MAXCDNAME +#define NS_MAXDNAME MAXDNAME +#define NS_MAXLABEL MAXLABEL +#endif + +/* Max MTU - defines dhcp option length */ +#define IP_UDP_SIZE 28 +#define MTU_MAX 1500 - IP_UDP_SIZE +#define MTU_MIN 576 + IP_UDP_SIZE + +#define OT_REQUEST (1 << 0) +#define OT_UINT8 (1 << 1) +#define OT_INT8 (1 << 2) +#define OT_UINT16 (1 << 3) +#define OT_INT16 (1 << 4) +#define OT_UINT32 (1 << 5) +#define OT_INT32 (1 << 6) +#define OT_ADDRIPV4 (1 << 7) +#define OT_STRING (1 << 8) +#define OT_ARRAY (1 << 9) +#define OT_RFC3361 (1 << 10) +#define OT_RFC1035 (1 << 11) +#define OT_RFC3442 (1 << 12) +#define OT_OPTIONAL (1 << 13) +#define OT_ADDRIPV6 (1 << 14) +#define OT_BINHEX (1 << 15) +#define OT_FLAG (1 << 16) +#define OT_NOREQ (1 << 17) +#define OT_EMBED (1 << 18) +#define OT_ENCAP (1 << 19) +#define OT_INDEX (1 << 20) +#define OT_OPTION (1 << 21) +#define OT_DOMAIN (1 << 22) +#define OT_ASCII (1 << 23) +#define OT_RAW (1 << 24) +#define OT_ESCSTRING (1 << 25) +#define OT_ESCFILE (1 << 26) +#define OT_BITFLAG (1 << 27) +#define OT_RESERVED (1 << 28) + +#define DHC_REQ(r, n, o) \ + (has_option_mask((r), (o)) && !has_option_mask((n), (o))) + +#define DHC_REQOPT(o, r, n) \ + (!((o)->type & OT_NOREQ) && \ + ((o)->type & OT_REQUEST || has_option_mask((r), (o)->option)) && \ + !has_option_mask((n), (o)->option)) + +struct dhcp_opt { + uint32_t option; /* Also used for IANA Enterpise Number */ + int type; + size_t len; + char *var; + + int index; /* Index counter for many instances of the same option */ + char bitflags[8]; + + /* Embedded options. + * The option code is irrelevant here. */ + struct dhcp_opt *embopts; + size_t embopts_len; + + /* Encapsulated options */ + struct dhcp_opt *encopts; + size_t encopts_len; +}; + +const char *dhcp_get_hostname(char *, size_t, const struct if_options *); +struct dhcp_opt *vivso_find(uint32_t, const void *); + +ssize_t dhcp_vendor(char *, size_t); + +void dhcp_print_option_encoding(const struct dhcp_opt *opt, int cols); +#define add_option_mask(var, val) \ + ((var)[(val) >> 3] = (uint8_t)((var)[(val) >> 3] | 1 << ((val) & 7))) +#define del_option_mask(var, val) \ + ((var)[(val) >> 3] = (uint8_t)((var)[(val) >> 3] & ~(1 << ((val) & 7)))) +#define has_option_mask(var, val) \ + ((var)[(val) >> 3] & (uint8_t)(1 << ((val) & 7))) +int make_option_mask(const struct dhcp_opt *, size_t, + const struct dhcp_opt *, size_t, + uint8_t *, const char *, int); + +size_t encode_rfc1035(const char *src, uint8_t *dst); +ssize_t decode_rfc1035(char *, size_t, const uint8_t *, size_t); +ssize_t print_string(char *, size_t, int, const uint8_t *, size_t); +int dhcp_set_leasefile(char *, size_t, int, const struct interface *); + +void dhcp_envoption(struct dhcpcd_ctx *, + FILE *, const char *, const char *, struct dhcp_opt *, + const uint8_t *(*dgetopt)(struct dhcpcd_ctx *, + size_t *, unsigned int *, size_t *, + const uint8_t *, size_t, struct dhcp_opt **), + const uint8_t *od, size_t ol); +void dhcp_zero_index(struct dhcp_opt *); + +ssize_t dhcp_readfile(struct dhcpcd_ctx *, const char *, void *, size_t); +ssize_t dhcp_writefile(struct dhcpcd_ctx *, const char *, mode_t, + const void *, size_t); +int dhcp_filemtime(struct dhcpcd_ctx *, const char *, time_t *); +int dhcp_unlink(struct dhcpcd_ctx *, const char *); +size_t dhcp_read_hwaddr_aton(struct dhcpcd_ctx *, uint8_t **, const char *); +#endif diff --git a/src/dhcp.c b/src/dhcp.c new file mode 100644 index 000000000000..fbed2f3ca2e2 --- /dev/null +++ b/src/dhcp.c @@ -0,0 +1,4314 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#include <sys/param.h> +#include <sys/socket.h> + +#include <arpa/inet.h> +#include <net/if.h> +#include <net/route.h> +#include <netinet/if_ether.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include <netinet/udp.h> +#undef __FAVOR_BSD + +#ifdef AF_LINK +# include <net/if_dl.h> +#endif + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <syslog.h> + +#define ELOOP_QUEUE ELOOP_DHCP +#include "config.h" +#include "arp.h" +#include "bpf.h" +#include "common.h" +#include "dhcp.h" +#include "dhcpcd.h" +#include "dhcp-common.h" +#include "duid.h" +#include "eloop.h" +#include "if.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "logerr.h" +#include "privsep.h" +#include "sa.h" +#include "script.h" + +#define DAD "Duplicate address detected" +#define DHCP_MIN_LEASE 20 + +#define IPV4A ADDRIPV4 | ARRAY +#define IPV4R ADDRIPV4 | REQUEST + +/* We should define a maximum for the NAK exponential backoff */ +#define NAKOFF_MAX 60 + +/* Wait N nanoseconds between sending a RELEASE and dropping the address. + * This gives the kernel enough time to actually send it. */ +#define RELEASE_DELAY_S 0 +#define RELEASE_DELAY_NS 10000000 + +#ifndef IPDEFTTL +#define IPDEFTTL 64 /* RFC1340 */ +#endif + +/* Support older systems with different defines */ +#if !defined(IP_RECVPKTINFO) && defined(IP_PKTINFO) +#define IP_RECVPKTINFO IP_PKTINFO +#endif + +/* Assert the correct structure size for on wire */ +__CTASSERT(sizeof(struct ip) == 20); +__CTASSERT(sizeof(struct udphdr) == 8); +__CTASSERT(sizeof(struct bootp) == 300); + +struct dhcp_op { + uint8_t value; + const char *name; +}; + +static const struct dhcp_op dhcp_ops[] = { + { DHCP_DISCOVER, "DISCOVER" }, + { DHCP_OFFER, "OFFER" }, + { DHCP_REQUEST, "REQUEST" }, + { DHCP_DECLINE, "DECLINE" }, + { DHCP_ACK, "ACK" }, + { DHCP_NAK, "NAK" }, + { DHCP_RELEASE, "RELEASE" }, + { DHCP_INFORM, "INFORM" }, + { DHCP_FORCERENEW, "FORCERENEW"}, + { 0, NULL } +}; + +static const char * const dhcp_params[] = { + "ip_address", + "subnet_cidr", + "network_number", + "filename", + "server_name", + NULL +}; + +static int dhcp_openbpf(struct interface *); +static void dhcp_start1(void *); +#if defined(ARP) && (!defined(KERNEL_RFC5227) || defined(ARPING)) +static void dhcp_arp_found(struct arp_state *, const struct arp_msg *); +#endif +static void dhcp_handledhcp(struct interface *, struct bootp *, size_t, + const struct in_addr *); +static void dhcp_handleifudp(void *); +static int dhcp_initstate(struct interface *); + +void +dhcp_printoptions(const struct dhcpcd_ctx *ctx, + const struct dhcp_opt *opts, size_t opts_len) +{ + const char * const *p; + size_t i, j; + const struct dhcp_opt *opt, *opt2; + int cols; + + for (p = dhcp_params; *p; p++) + printf(" %s\n", *p); + + for (i = 0, opt = ctx->dhcp_opts; i < ctx->dhcp_opts_len; i++, opt++) { + for (j = 0, opt2 = opts; j < opts_len; j++, opt2++) + if (opt->option == opt2->option) + break; + if (j == opts_len) { + cols = printf("%03d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } + } + for (i = 0, opt = opts; i < opts_len; i++, opt++) { + cols = printf("%03d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } +} + +static const uint8_t * +get_option(struct dhcpcd_ctx *ctx, + const struct bootp *bootp, size_t bootp_len, + unsigned int opt, size_t *opt_len) +{ + const uint8_t *p, *e; + uint8_t l, o, ol, overl, *bp; + const uint8_t *op; + size_t bl; + + if (bootp == NULL || bootp_len < DHCP_MIN_LEN) { + errno = EINVAL; + return NULL; + } + + /* Check we have the magic cookie */ + if (!IS_DHCP(bootp)) { + errno = ENOTSUP; + return NULL; + } + + p = bootp->vend + 4; /* options after the 4 byte cookie */ + e = (const uint8_t *)bootp + bootp_len; + ol = o = overl = 0; + bp = NULL; + op = NULL; + bl = 0; + while (p < e) { + o = *p++; + switch (o) { + case DHO_PAD: + /* No length to read */ + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl = (uint8_t)(overl & ~1); + p = bootp->file; + e = p + sizeof(bootp->file); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl = (uint8_t)(overl & ~2); + p = bootp->sname; + e = p + sizeof(bootp->sname); + } else + goto exit; + /* No length to read */ + continue; + } + + /* Check we can read the length */ + if (p == e) { + errno = EINVAL; + return NULL; + } + l = *p++; + + /* Check we can read the option data, if present */ + if (p + l > e) { + errno = EINVAL; + return NULL; + } + + if (o == DHO_OPTSOVERLOADED) { + /* Ensure we only get this option once by setting + * the last bit as well as the value. + * This is valid because only the first two bits + * actually mean anything in RFC2132 Section 9.3 */ + if (l == 1 && !overl) + overl = 0x80 | p[0]; + } + + if (o == opt) { + if (op) { + /* We must concatonate the options. */ + if (bl + l > ctx->opt_buffer_len) { + size_t pos; + uint8_t *nb; + + if (bp) + pos = (size_t) + (bp - ctx->opt_buffer); + else + pos = 0; + nb = realloc(ctx->opt_buffer, bl + l); + if (nb == NULL) + return NULL; + ctx->opt_buffer = nb; + ctx->opt_buffer_len = bl + l; + bp = ctx->opt_buffer + pos; + } + if (bp == NULL) + bp = ctx->opt_buffer; + memcpy(bp, op, ol); + bp += ol; + } + ol = l; + op = p; + bl += ol; + } + p += l; + } + +exit: + if (opt_len) + *opt_len = bl; + if (bp) { + memcpy(bp, op, ol); + return (const uint8_t *)ctx->opt_buffer; + } + if (op) + return op; + errno = ENOENT; + return NULL; +} + +static int +get_option_addr(struct dhcpcd_ctx *ctx, + struct in_addr *a, const struct bootp *bootp, size_t bootp_len, + uint8_t option) +{ + const uint8_t *p; + size_t len; + + p = get_option(ctx, bootp, bootp_len, option, &len); + if (!p || len < (ssize_t)sizeof(a->s_addr)) + return -1; + memcpy(&a->s_addr, p, sizeof(a->s_addr)); + return 0; +} + +static int +get_option_uint32(struct dhcpcd_ctx *ctx, + uint32_t *i, const struct bootp *bootp, size_t bootp_len, uint8_t option) +{ + const uint8_t *p; + size_t len; + uint32_t d; + + p = get_option(ctx, bootp, bootp_len, option, &len); + if (!p || len < (ssize_t)sizeof(d)) + return -1; + memcpy(&d, p, sizeof(d)); + if (i) + *i = ntohl(d); + return 0; +} + +static int +get_option_uint16(struct dhcpcd_ctx *ctx, + uint16_t *i, const struct bootp *bootp, size_t bootp_len, uint8_t option) +{ + const uint8_t *p; + size_t len; + uint16_t d; + + p = get_option(ctx, bootp, bootp_len, option, &len); + if (!p || len < (ssize_t)sizeof(d)) + return -1; + memcpy(&d, p, sizeof(d)); + if (i) + *i = ntohs(d); + return 0; +} + +static int +get_option_uint8(struct dhcpcd_ctx *ctx, + uint8_t *i, const struct bootp *bootp, size_t bootp_len, uint8_t option) +{ + const uint8_t *p; + size_t len; + + p = get_option(ctx, bootp, bootp_len, option, &len); + if (!p || len < (ssize_t)sizeof(*p)) + return -1; + if (i) + *i = *(p); + return 0; +} + +ssize_t +print_rfc3442(FILE *fp, const uint8_t *data, size_t data_len) +{ + const uint8_t *p = data, *e; + size_t ocets; + uint8_t cidr; + struct in_addr addr; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (data_len < 5) { + errno = EINVAL; + return -1; + } + + e = p + data_len; + while (p < e) { + if (p != data) { + if (fputc(' ', fp) == EOF) + return -1; + } + cidr = *p++; + if (cidr > 32) { + errno = EINVAL; + return -1; + } + ocets = (size_t)(cidr + 7) / NBBY; + if (p + 4 + ocets > e) { + errno = ERANGE; + return -1; + } + /* If we have ocets then we have a destination and netmask */ + addr.s_addr = 0; + if (ocets > 0) { + memcpy(&addr.s_addr, p, ocets); + p += ocets; + } + if (fprintf(fp, "%s/%d", inet_ntoa(addr), cidr) == -1) + return -1; + + /* Finally, snag the router */ + memcpy(&addr.s_addr, p, 4); + p += 4; + if (fprintf(fp, " %s", inet_ntoa(addr)) == -1) + return -1; + } + + if (fputc('\0', fp) == EOF) + return -1; + return 1; +} + +static int +decode_rfc3442_rt(rb_tree_t *routes, struct interface *ifp, + const uint8_t *data, size_t dl, const struct bootp *bootp) +{ + const uint8_t *p = data; + const uint8_t *e; + uint8_t cidr; + size_t ocets; + struct rt *rt = NULL; + struct in_addr dest, netmask, gateway; + int n; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (dl < 5) { + errno = EINVAL; + return -1; + } + + n = 0; + e = p + dl; + while (p < e) { + cidr = *p++; + if (cidr > 32) { + errno = EINVAL; + return -1; + } + + ocets = (size_t)(cidr + 7) / NBBY; + if (p + 4 + ocets > e) { + errno = ERANGE; + return -1; + } + + if ((rt = rt_new(ifp)) == NULL) + return -1; + + /* If we have ocets then we have a destination and netmask */ + dest.s_addr = 0; + if (ocets > 0) { + memcpy(&dest.s_addr, p, ocets); + p += ocets; + netmask.s_addr = htonl(~0U << (32 - cidr)); + } else + netmask.s_addr = 0; + + /* Finally, snag the router */ + memcpy(&gateway.s_addr, p, 4); + p += 4; + + /* An on-link host route is normally set by having the + * gateway match the destination or assigned address */ + if (gateway.s_addr == dest.s_addr || + (gateway.s_addr == bootp->yiaddr || + gateway.s_addr == bootp->ciaddr)) + { + gateway.s_addr = INADDR_ANY; + netmask.s_addr = INADDR_BROADCAST; + } + if (netmask.s_addr == INADDR_BROADCAST) + rt->rt_flags = RTF_HOST; + + sa_in_init(&rt->rt_dest, &dest); + sa_in_init(&rt->rt_netmask, &netmask); + sa_in_init(&rt->rt_gateway, &gateway); + if (rt_proto_add(routes, rt)) + n = 1; + } + return n; +} + +ssize_t +print_rfc3361(FILE *fp, const uint8_t *data, size_t dl) +{ + uint8_t enc; + char sip[NS_MAXDNAME]; + struct in_addr addr; + + if (dl < 2) { + errno = EINVAL; + return 0; + } + + enc = *data++; + dl--; + switch (enc) { + case 0: + if (decode_rfc1035(sip, sizeof(sip), data, dl) == -1) + return -1; + if (efprintf(fp, "%s", sip) == -1) + return -1; + break; + case 1: + if (dl % 4 != 0) { + errno = EINVAL; + break; + } + addr.s_addr = INADDR_BROADCAST; + for (; + dl != 0; + data += sizeof(addr.s_addr), dl -= sizeof(addr.s_addr)) + { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + if (fprintf(fp, "%s", inet_ntoa(addr)) == -1) + return -1; + if (dl != sizeof(addr.s_addr)) { + if (fputc(' ', fp) == EOF) + return -1; + } + } + if (fputc('\0', fp) == EOF) + return -1; + break; + default: + errno = EINVAL; + return 0; + } + + return 1; +} + +static char * +get_option_string(struct dhcpcd_ctx *ctx, + const struct bootp *bootp, size_t bootp_len, uint8_t option) +{ + size_t len; + const uint8_t *p; + char *s; + + p = get_option(ctx, bootp, bootp_len, option, &len); + if (!p || len == 0 || *p == '\0') + return NULL; + + s = malloc(sizeof(char) * (len + 1)); + if (s) { + memcpy(s, p, len); + s[len] = '\0'; + } + return s; +} + +/* This calculates the netmask that we should use for static routes. + * This IS different from the calculation used to calculate the netmask + * for an interface address. */ +static uint32_t +route_netmask(uint32_t ip_in) +{ + /* used to be unsigned long - check if error */ + uint32_t p = ntohl(ip_in); + uint32_t t; + + if (IN_CLASSA(p)) + t = ~IN_CLASSA_NET; + else { + if (IN_CLASSB(p)) + t = ~IN_CLASSB_NET; + else { + if (IN_CLASSC(p)) + t = ~IN_CLASSC_NET; + else + t = 0; + } + } + + while (t & p) + t >>= 1; + + return (htonl(~t)); +} + +/* We need to obey routing options. + * If we have a CSR then we only use that. + * Otherwise we add static routes and then routers. */ +static int +get_option_routes(rb_tree_t *routes, struct interface *ifp, + const struct bootp *bootp, size_t bootp_len) +{ + struct if_options *ifo = ifp->options; + const uint8_t *p; + const uint8_t *e; + struct rt *rt = NULL; + struct in_addr dest, netmask, gateway; + size_t len; + const char *csr = ""; + int n; + + /* If we have CSR's then we MUST use these only */ + if (!has_option_mask(ifo->nomask, DHO_CSR)) + p = get_option(ifp->ctx, bootp, bootp_len, DHO_CSR, &len); + else + p = NULL; + /* Check for crappy MS option */ + if (!p && !has_option_mask(ifo->nomask, DHO_MSCSR)) { + p = get_option(ifp->ctx, bootp, bootp_len, DHO_MSCSR, &len); + if (p) + csr = "MS "; + } + if (p && (n = decode_rfc3442_rt(routes, ifp, p, len, bootp)) != -1) { + const struct dhcp_state *state; + + state = D_CSTATE(ifp); + if (!(ifo->options & DHCPCD_CSR_WARNED) && + !(state->added & STATE_FAKE)) + { + logdebugx("%s: using %sClassless Static Routes", + ifp->name, csr); + ifo->options |= DHCPCD_CSR_WARNED; + } + return n; + } + + n = 0; + /* OK, get our static routes first. */ + if (!has_option_mask(ifo->nomask, DHO_STATICROUTE)) + p = get_option(ifp->ctx, bootp, bootp_len, + DHO_STATICROUTE, &len); + else + p = NULL; + /* RFC 2131 Section 5.8 states length MUST be in multiples of 8 */ + if (p && len % 8 == 0) { + e = p + len; + while (p < e) { + memcpy(&dest.s_addr, p, sizeof(dest.s_addr)); + p += 4; + memcpy(&gateway.s_addr, p, sizeof(gateway.s_addr)); + p += 4; + /* RFC 2131 Section 5.8 states default route is + * illegal */ + if (gateway.s_addr == INADDR_ANY) + continue; + if ((rt = rt_new(ifp)) == NULL) + return -1; + + /* A on-link host route is normally set by having the + * gateway match the destination or assigned address */ + if (gateway.s_addr == dest.s_addr || + (gateway.s_addr == bootp->yiaddr || + gateway.s_addr == bootp->ciaddr)) + { + gateway.s_addr = INADDR_ANY; + netmask.s_addr = INADDR_BROADCAST; + } else + netmask.s_addr = route_netmask(dest.s_addr); + if (netmask.s_addr == INADDR_BROADCAST) + rt->rt_flags = RTF_HOST; + + sa_in_init(&rt->rt_dest, &dest); + sa_in_init(&rt->rt_netmask, &netmask); + sa_in_init(&rt->rt_gateway, &gateway); + if (rt_proto_add(routes, rt)) + n++; + } + } + + /* Now grab our routers */ + if (!has_option_mask(ifo->nomask, DHO_ROUTER)) + p = get_option(ifp->ctx, bootp, bootp_len, DHO_ROUTER, &len); + else + p = NULL; + if (p && len % 4 == 0) { + e = p + len; + dest.s_addr = INADDR_ANY; + netmask.s_addr = INADDR_ANY; + while (p < e) { + if ((rt = rt_new(ifp)) == NULL) + return -1; + memcpy(&gateway.s_addr, p, sizeof(gateway.s_addr)); + p += 4; + sa_in_init(&rt->rt_dest, &dest); + sa_in_init(&rt->rt_netmask, &netmask); + sa_in_init(&rt->rt_gateway, &gateway); + if (rt_proto_add(routes, rt)) + n++; + } + } + + return n; +} + +uint16_t +dhcp_get_mtu(const struct interface *ifp) +{ + const struct dhcp_state *state; + uint16_t mtu; + + if (ifp->options->mtu) + return (uint16_t)ifp->options->mtu; + mtu = 0; /* bogus gcc warning */ + if ((state = D_CSTATE(ifp)) == NULL || + has_option_mask(ifp->options->nomask, DHO_MTU) || + get_option_uint16(ifp->ctx, &mtu, + state->new, state->new_len, DHO_MTU) == -1) + return 0; + return mtu; +} + +/* Grab our routers from the DHCP message and apply any MTU value + * the message contains */ +int +dhcp_get_routes(rb_tree_t *routes, struct interface *ifp) +{ + const struct dhcp_state *state; + + if ((state = D_CSTATE(ifp)) == NULL || !(state->added & STATE_ADDED)) + return 0; + return get_option_routes(routes, ifp, state->new, state->new_len); +} + +/* Assumes DHCP options */ +static int +dhcp_message_add_addr(struct bootp *bootp, + uint8_t type, struct in_addr addr) +{ + uint8_t *p; + size_t len; + + p = bootp->vend; + while (*p != DHO_END) { + p++; + p += *p + 1; + } + + len = (size_t)(p - bootp->vend); + if (len + 6 > sizeof(bootp->vend)) { + errno = ENOMEM; + return -1; + } + + *p++ = type; + *p++ = 4; + memcpy(p, &addr.s_addr, 4); + p += 4; + *p = DHO_END; + return 0; +} + +static ssize_t +make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type) +{ + struct bootp *bootp; + uint8_t *lp, *p, *e; + uint8_t *n_params = NULL; + uint32_t ul; + uint16_t sz; + size_t len, i; + const struct dhcp_opt *opt; + struct if_options *ifo = ifp->options; + const struct dhcp_state *state = D_CSTATE(ifp); + const struct dhcp_lease *lease = &state->lease; + char hbuf[HOSTNAME_MAX_LEN + 1]; + const char *hostname; + const struct vivco *vivco; + int mtu; +#ifdef AUTH + uint8_t *auth, auth_len; +#endif + + if ((mtu = if_getmtu(ifp)) == -1) + logerr("%s: if_getmtu", ifp->name); + else if (mtu < MTU_MIN) { + if (if_setmtu(ifp, MTU_MIN) == -1) + logerr("%s: if_setmtu", ifp->name); + mtu = MTU_MIN; + } + + if (ifo->options & DHCPCD_BOOTP) + bootp = calloc(1, sizeof (*bootp)); + else + /* Make the maximal message we could send */ + bootp = calloc(1, (size_t)(mtu - IP_UDP_SIZE)); + + if (bootp == NULL) + return -1; + *bootpm = bootp; + + if (state->addr != NULL && + (type == DHCP_INFORM || type == DHCP_RELEASE || + (type == DHCP_REQUEST && + state->addr->mask.s_addr == lease->mask.s_addr && + (state->new == NULL || IS_DHCP(state->new)) && + !(state->added & (STATE_FAKE | STATE_EXPIRED))))) + bootp->ciaddr = state->addr->addr.s_addr; + + bootp->op = BOOTREQUEST; + bootp->htype = (uint8_t)ifp->hwtype; + if (ifp->hwlen != 0 && ifp->hwlen < sizeof(bootp->chaddr)) { + bootp->hlen = (uint8_t)ifp->hwlen; + memcpy(&bootp->chaddr, &ifp->hwaddr, ifp->hwlen); + } + + if (ifo->options & DHCPCD_BROADCAST && + bootp->ciaddr == 0 && + type != DHCP_DECLINE && + type != DHCP_RELEASE) + bootp->flags = htons(BROADCAST_FLAG); + + if (type != DHCP_DECLINE && type != DHCP_RELEASE) { + struct timespec tv; + unsigned long long secs; + + clock_gettime(CLOCK_MONOTONIC, &tv); + secs = eloop_timespec_diff(&tv, &state->started, NULL); + if (secs > UINT16_MAX) + bootp->secs = htons((uint16_t)UINT16_MAX); + else + bootp->secs = htons((uint16_t)secs); + } + + bootp->xid = htonl(state->xid); + + if (ifo->options & DHCPCD_BOOTP) + return sizeof(*bootp); + + p = bootp->vend; + e = (uint8_t *)bootp + (mtu - IP_UDP_SIZE) - 1; /* -1 for DHO_END */ + + ul = htonl(MAGIC_COOKIE); + memcpy(p, &ul, sizeof(ul)); + p += sizeof(ul); + +#define AREA_LEFT (size_t)(e - p) +#define AREA_FIT(s) if ((s) > AREA_LEFT) goto toobig +#define AREA_CHECK(s) if ((s) + 2UL > AREA_LEFT) goto toobig +#define PUT_ADDR(o, a) do { \ + AREA_CHECK(4); \ + *p++ = (o); \ + *p++ = 4; \ + memcpy(p, &(a)->s_addr, 4); \ + p += 4; \ +} while (0 /* CONSTCOND */) + + /* Options are listed in numerical order as per RFC 7844 Section 3.1 + * XXX: They should be randomised. */ + + bool putip = false; + if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { + if (type == DHCP_DECLINE || + (type == DHCP_REQUEST && + (state->addr == NULL || + state->added & (STATE_FAKE | STATE_EXPIRED) || + lease->addr.s_addr != state->addr->addr.s_addr))) + { + putip = true; + PUT_ADDR(DHO_IPADDRESS, &lease->addr); + } + } + + AREA_CHECK(3); + *p++ = DHO_MESSAGETYPE; + *p++ = 1; + *p++ = type; + + if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { + if (type == DHCP_RELEASE || putip) { + if (lease->server.s_addr) + PUT_ADDR(DHO_SERVERID, &lease->server); + } + } + + if (type == DHCP_DECLINE) { + len = strlen(DAD); + if (len > AREA_LEFT) { + *p++ = DHO_MESSAGE; + *p++ = (uint8_t)len; + memcpy(p, DAD, len); + p += len; + } + } + +#define DHCP_DIR(type) ((type) == DHCP_DISCOVER || (type) == DHCP_INFORM || \ + (type) == DHCP_REQUEST) + + if (DHCP_DIR(type)) { + /* vendor is already encoded correctly, so just add it */ + if (ifo->vendor[0]) { + AREA_CHECK(ifo->vendor[0]); + *p++ = DHO_VENDOR; + memcpy(p, ifo->vendor, (size_t)ifo->vendor[0] + 1); + p += ifo->vendor[0] + 1; + } + } + + if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST) + PUT_ADDR(DHO_IPADDRESS, &ifo->req_addr); + + if (DHCP_DIR(type)) { + if (type != DHCP_INFORM) { + if (ifo->leasetime != 0) { + AREA_CHECK(4); + *p++ = DHO_LEASETIME; + *p++ = 4; + ul = htonl(ifo->leasetime); + memcpy(p, &ul, 4); + p += 4; + } + } + + AREA_CHECK(0); + *p++ = DHO_PARAMETERREQUESTLIST; + n_params = p; + *p++ = 0; + for (i = 0, opt = ifp->ctx->dhcp_opts; + i < ifp->ctx->dhcp_opts_len; + i++, opt++) + { + if (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask)) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) + continue; + AREA_FIT(1); + *p++ = (uint8_t)opt->option; + } + for (i = 0, opt = ifo->dhcp_override; + i < ifo->dhcp_override_len; + i++, opt++) + { + /* Check if added above */ + for (lp = n_params + 1; lp < p; lp++) + if (*lp == (uint8_t)opt->option) + break; + if (lp < p) + continue; + if (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask)) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) + continue; + AREA_FIT(1); + *p++ = (uint8_t)opt->option; + } + *n_params = (uint8_t)(p - n_params - 1); + + if (mtu != -1 && + !(has_option_mask(ifo->nomask, DHO_MAXMESSAGESIZE))) + { + AREA_CHECK(2); + *p++ = DHO_MAXMESSAGESIZE; + *p++ = 2; + sz = htons((uint16_t)(mtu - IP_UDP_SIZE)); + memcpy(p, &sz, 2); + p += 2; + } + + if (ifo->userclass[0] && + !has_option_mask(ifo->nomask, DHO_USERCLASS)) + { + AREA_CHECK(ifo->userclass[0]); + *p++ = DHO_USERCLASS; + memcpy(p, ifo->userclass, + (size_t)ifo->userclass[0] + 1); + p += ifo->userclass[0] + 1; + } + } + + if (state->clientid) { + AREA_CHECK(state->clientid[0]); + *p++ = DHO_CLIENTID; + memcpy(p, state->clientid, (size_t)state->clientid[0] + 1); + p += state->clientid[0] + 1; + } + + if (DHCP_DIR(type) && + !has_option_mask(ifo->nomask, DHO_VENDORCLASSID) && + ifo->vendorclassid[0]) + { + AREA_CHECK(ifo->vendorclassid[0]); + *p++ = DHO_VENDORCLASSID; + memcpy(p, ifo->vendorclassid, (size_t)ifo->vendorclassid[0]+1); + p += ifo->vendorclassid[0] + 1; + } + + if (type == DHCP_DISCOVER && + !(ifp->ctx->options & DHCPCD_TEST) && + DHC_REQ(ifo->requestmask, ifo->nomask, DHO_RAPIDCOMMIT)) + { + /* RFC 4039 Section 3 */ + AREA_CHECK(0); + *p++ = DHO_RAPIDCOMMIT; + *p++ = 0; + } + + if (DHCP_DIR(type)) { + hostname = dhcp_get_hostname(hbuf, sizeof(hbuf), ifo); + + /* + * RFC4702 3.1 States that if we send the Client FQDN option + * then we MUST NOT also send the Host Name option. + * Technically we could, but that is not RFC conformant and + * also seems to break some DHCP server implemetations such as + * Windows. On the other hand, ISC dhcpd is just as non RFC + * conformant by not accepting a partially qualified FQDN. + */ + if (ifo->fqdn != FQDN_DISABLE) { + /* IETF DHC-FQDN option (81), RFC4702 */ + i = 3; + if (hostname) + i += encode_rfc1035(hostname, NULL); + AREA_CHECK(i); + *p++ = DHO_FQDN; + *p++ = (uint8_t)i; + /* + * Flags: 0000NEOS + * S: 1 => Client requests Server to update + * a RR in DNS as well as PTR + * O: 1 => Server indicates to client that + * DNS has been updated + * E: 1 => Name data is DNS format + * N: 1 => Client requests Server to not + * update DNS + */ + if (hostname) + *p++ = (uint8_t)((ifo->fqdn & 0x09) | 0x04); + else + *p++ = (FQDN_NONE & 0x09) | 0x04; + *p++ = 0; /* from server for PTR RR */ + *p++ = 0; /* from server for A RR if S=1 */ + if (hostname) { + i = encode_rfc1035(hostname, p); + p += i; + } + } else if (ifo->options & DHCPCD_HOSTNAME && hostname) { + len = strlen(hostname); + AREA_CHECK(len); + *p++ = DHO_HOSTNAME; + *p++ = (uint8_t)len; + memcpy(p, hostname, len); + p += len; + } + } + +#ifdef AUTH + auth = NULL; /* appease GCC */ + auth_len = 0; + if (ifo->auth.options & DHCPCD_AUTH_SEND) { + ssize_t alen = dhcp_auth_encode(ifp->ctx, &ifo->auth, + state->auth.token, + NULL, 0, 4, type, NULL, 0); + if (alen != -1 && alen > UINT8_MAX) { + errno = ERANGE; + alen = -1; + } + if (alen == -1) + logerr("%s: dhcp_auth_encode", ifp->name); + else if (alen != 0) { + auth_len = (uint8_t)alen; + AREA_CHECK(auth_len); + *p++ = DHO_AUTHENTICATION; + *p++ = auth_len; + auth = p; + p += auth_len; + } + } +#endif + + /* RFC 2563 Auto Configure */ + if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL && + !(has_option_mask(ifo->nomask, DHO_AUTOCONFIGURE))) + { + AREA_CHECK(1); + *p++ = DHO_AUTOCONFIGURE; + *p++ = 1; + *p++ = 1; + } + + if (DHCP_DIR(type)) { + if (ifo->mudurl[0]) { + AREA_CHECK(ifo->mudurl[0]); + *p++ = DHO_MUDURL; + memcpy(p, ifo->mudurl, (size_t)ifo->mudurl[0] + 1); + p += ifo->mudurl[0] + 1; + } + + if (ifo->vivco_len && + !has_option_mask(ifo->nomask, DHO_VIVCO)) + { + AREA_CHECK(sizeof(ul)); + *p++ = DHO_VIVCO; + lp = p++; + *lp = sizeof(ul); + ul = htonl(ifo->vivco_en); + memcpy(p, &ul, sizeof(ul)); + p += sizeof(ul); + for (i = 0, vivco = ifo->vivco; + i < ifo->vivco_len; + i++, vivco++) + { + AREA_FIT(vivco->len); + if (vivco->len + 2 + *lp > 255) { + logerrx("%s: VIVCO option too big", + ifp->name); + free(bootp); + return -1; + } + *p++ = (uint8_t)vivco->len; + memcpy(p, vivco->data, vivco->len); + p += vivco->len; + *lp = (uint8_t)(*lp + vivco->len + 1); + } + } + +#ifdef AUTH + if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != + DHCPCD_AUTH_SENDREQUIRE && + !has_option_mask(ifo->nomask, DHO_FORCERENEW_NONCE)) + { + /* We support HMAC-MD5 */ + AREA_CHECK(1); + *p++ = DHO_FORCERENEW_NONCE; + *p++ = 1; + *p++ = AUTH_ALG_HMAC_MD5; + } +#endif + } + + *p++ = DHO_END; + len = (size_t)(p - (uint8_t *)bootp); + + /* Pad out to the BOOTP message length. + * Even if we send a DHCP packet with a variable length vendor area, + * some servers / relay agents don't like packets smaller than + * a BOOTP message which is fine because that's stipulated + * in RFC1542 section 2.1. */ + while (len < sizeof(*bootp)) { + *p++ = DHO_PAD; + len++; + } + +#ifdef AUTH + if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) + dhcp_auth_encode(ifp->ctx, &ifo->auth, state->auth.token, + (uint8_t *)bootp, len, 4, type, auth, auth_len); +#endif + + return (ssize_t)len; + +toobig: + logerrx("%s: DHCP message too big", ifp->name); + free(bootp); + return -1; +} + +static size_t +read_lease(struct interface *ifp, struct bootp **bootp) +{ + union { + struct bootp bootp; + uint8_t buf[FRAMELEN_MAX]; + } buf; + struct dhcp_state *state = D_STATE(ifp); + ssize_t sbytes; + size_t bytes; + uint8_t type; +#ifdef AUTH + const uint8_t *auth; + size_t auth_len; +#endif + + /* Safety */ + *bootp = NULL; + + if (state->leasefile[0] == '\0') { + logdebugx("reading standard input"); + sbytes = read(fileno(stdin), buf.buf, sizeof(buf.buf)); + } else { + logdebugx("%s: reading lease: %s", + ifp->name, state->leasefile); + sbytes = dhcp_readfile(ifp->ctx, state->leasefile, + buf.buf, sizeof(buf.buf)); + } + if (sbytes == -1) { + if (errno != ENOENT) + logerr("%s: %s", ifp->name, state->leasefile); + return 0; + } + bytes = (size_t)sbytes; + + /* Ensure the packet is at lease BOOTP sized + * with a vendor area of 4 octets + * (it should be more, and our read packet enforces this so this + * code should not be needed, but of course people could + * scribble whatever in the stored lease file. */ + if (bytes < DHCP_MIN_LEN) { + logerrx("%s: %s: truncated lease", ifp->name, __func__); + return 0; + } + + if (ifp->ctx->options & DHCPCD_DUMPLEASE) + goto out; + + /* We may have found a BOOTP server */ + if (get_option_uint8(ifp->ctx, &type, &buf.bootp, bytes, + DHO_MESSAGETYPE) == -1) + type = 0; + +#ifdef AUTH + /* Authenticate the message */ + auth = get_option(ifp->ctx, &buf.bootp, bytes, + DHO_AUTHENTICATION, &auth_len); + if (auth) { + if (dhcp_auth_validate(&state->auth, &ifp->options->auth, + &buf.bootp, bytes, 4, type, auth, auth_len) == NULL) + { + logerr("%s: authentication failed", ifp->name); + return 0; + } + if (state->auth.token) + logdebugx("%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + else + logdebugx("%s: accepted reconfigure key", ifp->name); + } else if ((ifp->options->auth.options & DHCPCD_AUTH_SENDREQUIRE) == + DHCPCD_AUTH_SENDREQUIRE) + { + logerrx("%s: authentication now required", ifp->name); + return 0; + } +#endif + +out: + *bootp = malloc(bytes); + if (*bootp == NULL) { + logerr(__func__); + return 0; + } + memcpy(*bootp, buf.buf, bytes); + return bytes; +} + +static const struct dhcp_opt * +dhcp_getoverride(const struct if_options *ifo, unsigned int o) +{ + size_t i; + const struct dhcp_opt *opt; + + for (i = 0, opt = ifo->dhcp_override; + i < ifo->dhcp_override_len; + i++, opt++) + { + if (opt->option == o) + return opt; + } + return NULL; +} + +static const uint8_t * +dhcp_getoption(struct dhcpcd_ctx *ctx, + size_t *os, unsigned int *code, size_t *len, + const uint8_t *od, size_t ol, struct dhcp_opt **oopt) +{ + size_t i; + struct dhcp_opt *opt; + + if (od) { + if (ol < 2) { + errno = EINVAL; + return NULL; + } + *os = 2; /* code + len */ + *code = (unsigned int)*od++; + *len = (size_t)*od++; + if (*len > ol - *os) { + errno = ERANGE; + return NULL; + } + } + + *oopt = NULL; + for (i = 0, opt = ctx->dhcp_opts; i < ctx->dhcp_opts_len; i++, opt++) { + if (opt->option == *code) { + *oopt = opt; + break; + } + } + + return od; +} + +ssize_t +dhcp_env(FILE *fenv, const char *prefix, const struct interface *ifp, + const struct bootp *bootp, size_t bootp_len) +{ + const struct if_options *ifo; + const uint8_t *p; + struct in_addr addr; + struct in_addr net; + struct in_addr brd; + struct dhcp_opt *opt, *vo; + size_t i, pl; + char safe[(BOOTP_FILE_LEN * 4) + 1]; + uint8_t overl = 0; + uint32_t en; + + ifo = ifp->options; + if (get_option_uint8(ifp->ctx, &overl, bootp, bootp_len, + DHO_OPTSOVERLOADED) == -1) + overl = 0; + + if (bootp->yiaddr || bootp->ciaddr) { + /* Set some useful variables that we derive from the DHCP + * message but are not necessarily in the options */ + addr.s_addr = bootp->yiaddr ? bootp->yiaddr : bootp->ciaddr; + if (efprintf(fenv, "%s_ip_address=%s", + prefix, inet_ntoa(addr)) == -1) + return -1; + if (get_option_addr(ifp->ctx, &net, + bootp, bootp_len, DHO_SUBNETMASK) == -1) { + net.s_addr = ipv4_getnetmask(addr.s_addr); + if (efprintf(fenv, "%s_subnet_mask=%s", + prefix, inet_ntoa(net)) == -1) + return -1; + } + if (efprintf(fenv, "%s_subnet_cidr=%d", + prefix, inet_ntocidr(net))== -1) + return -1; + if (get_option_addr(ifp->ctx, &brd, + bootp, bootp_len, DHO_BROADCAST) == -1) + { + brd.s_addr = addr.s_addr | ~net.s_addr; + if (efprintf(fenv, "%s_broadcast_address=%s", + prefix, inet_ntoa(brd)) == -1) + return -1; + } + addr.s_addr = bootp->yiaddr & net.s_addr; + if (efprintf(fenv, "%s_network_number=%s", + prefix, inet_ntoa(addr)) == -1) + return -1; + } + + if (*bootp->file && !(overl & 1)) { + print_string(safe, sizeof(safe), OT_STRING, + bootp->file, sizeof(bootp->file)); + if (efprintf(fenv, "%s_filename=%s", prefix, safe) == -1) + return -1; + } + if (*bootp->sname && !(overl & 2)) { + print_string(safe, sizeof(safe), OT_STRING | OT_DOMAIN, + bootp->sname, sizeof(bootp->sname)); + if (efprintf(fenv, "%s_server_name=%s", prefix, safe) == -1) + return -1; + } + + /* Zero our indexes */ + for (i = 0, opt = ifp->ctx->dhcp_opts; + i < ifp->ctx->dhcp_opts_len; + i++, opt++) + dhcp_zero_index(opt); + for (i = 0, opt = ifp->options->dhcp_override; + i < ifp->options->dhcp_override_len; + i++, opt++) + dhcp_zero_index(opt); + for (i = 0, opt = ifp->ctx->vivso; + i < ifp->ctx->vivso_len; + i++, opt++) + dhcp_zero_index(opt); + + for (i = 0, opt = ifp->ctx->dhcp_opts; + i < ifp->ctx->dhcp_opts_len; + i++, opt++) + { + if (has_option_mask(ifo->nomask, opt->option)) + continue; + if (dhcp_getoverride(ifo, opt->option)) + continue; + p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl); + if (p == NULL) + continue; + dhcp_envoption(ifp->ctx, fenv, prefix, ifp->name, + opt, dhcp_getoption, p, pl); + + if (opt->option != DHO_VIVSO || pl <= (int)sizeof(uint32_t)) + continue; + memcpy(&en, p, sizeof(en)); + en = ntohl(en); + vo = vivso_find(en, ifp); + if (vo == NULL) + continue; + /* Skip over en + total size */ + p += sizeof(en) + 1; + pl -= sizeof(en) + 1; + dhcp_envoption(ifp->ctx, fenv, prefix, ifp->name, + vo, dhcp_getoption, p, pl); + } + + for (i = 0, opt = ifo->dhcp_override; + i < ifo->dhcp_override_len; + i++, opt++) + { + if (has_option_mask(ifo->nomask, opt->option)) + continue; + p = get_option(ifp->ctx, bootp, bootp_len, opt->option, &pl); + if (p == NULL) + continue; + dhcp_envoption(ifp->ctx, fenv, prefix, ifp->name, + opt, dhcp_getoption, p, pl); + } + + return 1; +} + +static void +get_lease(struct interface *ifp, + struct dhcp_lease *lease, const struct bootp *bootp, size_t len) +{ + struct dhcpcd_ctx *ctx; + + assert(bootp != NULL); + + memcpy(&lease->cookie, bootp->vend, sizeof(lease->cookie)); + /* BOOTP does not set yiaddr for replies when ciaddr is set. */ + lease->addr.s_addr = bootp->yiaddr ? bootp->yiaddr : bootp->ciaddr; + ctx = ifp->ctx; + if (ifp->options->options & (DHCPCD_STATIC | DHCPCD_INFORM)) { + if (ifp->options->req_addr.s_addr != INADDR_ANY) { + lease->mask = ifp->options->req_mask; + if (ifp->options->req_brd.s_addr != INADDR_ANY) + lease->brd = ifp->options->req_brd; + else + lease->brd.s_addr = + lease->addr.s_addr | ~lease->mask.s_addr; + } else { + const struct ipv4_addr *ia; + + ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); + assert(ia != NULL); + lease->mask = ia->mask; + lease->brd = ia->brd; + } + } else { + if (get_option_addr(ctx, &lease->mask, bootp, len, + DHO_SUBNETMASK) == -1) + lease->mask.s_addr = + ipv4_getnetmask(lease->addr.s_addr); + if (get_option_addr(ctx, &lease->brd, bootp, len, + DHO_BROADCAST) == -1) + lease->brd.s_addr = + lease->addr.s_addr | ~lease->mask.s_addr; + } + if (get_option_uint32(ctx, &lease->leasetime, + bootp, len, DHO_LEASETIME) != 0) + lease->leasetime = DHCP_INFINITE_LIFETIME; + if (get_option_uint32(ctx, &lease->renewaltime, + bootp, len, DHO_RENEWALTIME) != 0) + lease->renewaltime = 0; + if (get_option_uint32(ctx, &lease->rebindtime, + bootp, len, DHO_REBINDTIME) != 0) + lease->rebindtime = 0; + if (get_option_addr(ctx, &lease->server, bootp, len, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; +} + +static const char * +get_dhcp_op(uint8_t type) +{ + const struct dhcp_op *d; + + for (d = dhcp_ops; d->name; d++) + if (d->value == type) + return d->name; + return NULL; +} + +static void +dhcp_fallback(void *arg) +{ + struct interface *iface; + + iface = (struct interface *)arg; + dhcpcd_selectprofile(iface, iface->options->fallback); + dhcpcd_startinterface(iface); +} + +static void +dhcp_new_xid(struct interface *ifp) +{ + struct dhcp_state *state; + const struct interface *ifp1; + const struct dhcp_state *state1; + + state = D_STATE(ifp); + if (ifp->options->options & DHCPCD_XID_HWADDR && + ifp->hwlen >= sizeof(state->xid)) + /* The lower bits are probably more unique on the network */ + memcpy(&state->xid, + (ifp->hwaddr + ifp->hwlen) - sizeof(state->xid), + sizeof(state->xid)); + else { +again: + state->xid = arc4random(); + } + + /* Ensure it's unique */ + TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) { + if (ifp == ifp1) + continue; + if ((state1 = D_CSTATE(ifp1)) == NULL) + continue; + if (state1->xid == state->xid) + break; + } + if (ifp1 != NULL) { + if (ifp->options->options & DHCPCD_XID_HWADDR && + ifp->hwlen >= sizeof(state->xid)) + { + logerrx("%s: duplicate xid on %s", + ifp->name, ifp1->name); + return; + } + goto again; + } + + /* We can't do this when sharing leases across interfaes */ +#if 0 + /* As the XID changes, re-apply the filter. */ + if (state->bpf_fd != -1) { + if (bpf_bootp(ifp, state->bpf_fd) == -1) + logerr(__func__); /* try to continue */ + } +#endif +} + +static void +dhcp_closebpf(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct dhcp_state *state = D_STATE(ifp); + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) + ps_bpf_closebootp(ifp); +#endif + + if (state->bpf != NULL) { + eloop_event_delete(ctx->eloop, state->bpf->bpf_fd); + bpf_close(state->bpf); + state->bpf = NULL; + } +} + +static void +dhcp_closeinet(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct dhcp_state *state = D_STATE(ifp); + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) { + if (state->addr != NULL) + ps_inet_closebootp(state->addr); + } +#endif + + if (state->udp_rfd != -1) { + eloop_event_delete(ctx->eloop, state->udp_rfd); + close(state->udp_rfd); + state->udp_rfd = -1; + } +} + +void +dhcp_close(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + + if (state == NULL) + return; + + dhcp_closebpf(ifp); + dhcp_closeinet(ifp); + + state->interval = 0; +} + +int +dhcp_openudp(struct in_addr *ia) +{ + int s; + struct sockaddr_in sin; + int n; + + if ((s = xsocket(PF_INET, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP)) == -1) + return -1; + + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) + goto errexit; +#ifdef IP_RECVIF + if (setsockopt(s, IPPROTO_IP, IP_RECVIF, &n, sizeof(n)) == -1) + goto errexit; +#else + if (setsockopt(s, IPPROTO_IP, IP_RECVPKTINFO, &n, sizeof(n)) == -1) + goto errexit; +#endif +#ifdef SO_RERROR + if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1) + goto errexit; +#endif + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(BOOTPC); + if (ia != NULL) + sin.sin_addr = *ia; + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) + goto errexit; + + return s; + +errexit: + close(s); + return -1; +} + +static uint16_t +in_cksum(const void *data, size_t len, uint32_t *isum) +{ + const uint16_t *word = data; + uint32_t sum = isum != NULL ? *isum : 0; + + for (; len > 1; len -= sizeof(*word)) + sum += *word++; + + if (len == 1) + sum += htons((uint16_t)(*(const uint8_t *)word << 8)); + + if (isum != NULL) + *isum = sum; + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + return (uint16_t)~sum; +} + +static struct bootp_pkt * +dhcp_makeudppacket(size_t *sz, const uint8_t *data, size_t length, + struct in_addr source, struct in_addr dest) +{ + struct bootp_pkt *udpp; + struct ip *ip; + struct udphdr *udp; + + if ((udpp = calloc(1, sizeof(*ip) + sizeof(*udp) + length)) == NULL) + return NULL; + ip = &udpp->ip; + udp = &udpp->udp; + + /* OK, this is important :) + * We copy the data to our packet and then create a small part of the + * ip structure and an invalid ip_len (basically udp length). + * We then fill the udp structure and put the checksum + * of the whole packet into the udp checksum. + * Finally we complete the ip structure and ip checksum. + * If we don't do the ordering like so then the udp checksum will be + * broken, so find another way of doing it! */ + + memcpy(&udpp->bootp, data, length); + + ip->ip_p = IPPROTO_UDP; + ip->ip_src.s_addr = source.s_addr; + if (dest.s_addr == 0) + ip->ip_dst.s_addr = INADDR_BROADCAST; + else + ip->ip_dst.s_addr = dest.s_addr; + + udp->uh_sport = htons(BOOTPC); + udp->uh_dport = htons(BOOTPS); + udp->uh_ulen = htons((uint16_t)(sizeof(*udp) + length)); + ip->ip_len = udp->uh_ulen; + udp->uh_sum = in_cksum(udpp, sizeof(*ip) + sizeof(*udp) + length, NULL); + + ip->ip_v = IPVERSION; + ip->ip_hl = sizeof(*ip) >> 2; + ip->ip_id = (uint16_t)arc4random_uniform(UINT16_MAX); + ip->ip_ttl = IPDEFTTL; + ip->ip_len = htons((uint16_t)(sizeof(*ip) + sizeof(*udp) + length)); + ip->ip_sum = in_cksum(ip, sizeof(*ip), NULL); + if (ip->ip_sum == 0) + ip->ip_sum = 0xffff; /* RFC 768 */ + + *sz = sizeof(*ip) + sizeof(*udp) + length; + return udpp; +} + +static ssize_t +dhcp_sendudp(struct interface *ifp, struct in_addr *to, void *data, size_t len) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_addr = *to, + .sin_port = htons(BOOTPS), +#ifdef HAVE_SA_LEN + .sin_len = sizeof(sin), +#endif + }; + struct udphdr udp = { + .uh_sport = htons(BOOTPC), + .uh_dport = htons(BOOTPS), + .uh_ulen = htons((uint16_t)(sizeof(udp) + len)), + }; + struct iovec iov[] = { + { .iov_base = &udp, .iov_len = sizeof(udp), }, + { .iov_base = data, .iov_len = len, }, + }; + struct msghdr msg = { + .msg_name = (void *)&sin, + .msg_namelen = sizeof(sin), + .msg_iov = iov, + .msg_iovlen = __arraycount(iov), + }; + struct dhcpcd_ctx *ctx = ifp->ctx; + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) + return ps_inet_sendbootp(ifp, &msg); +#endif + return sendmsg(ctx->udp_wfd, &msg, 0); +} + +static void +send_message(struct interface *ifp, uint8_t type, + void (*callback)(void *)) +{ + struct dhcp_state *state = D_STATE(ifp); + struct if_options *ifo = ifp->options; + struct bootp *bootp; + struct bootp_pkt *udp; + size_t len, ulen; + ssize_t r; + struct in_addr from, to; + unsigned int RT; + + if (callback == NULL) { + /* No carrier? Don't bother sending the packet. */ + if (!if_is_link_up(ifp)) + return; + logdebugx("%s: sending %s with xid 0x%x", + ifp->name, + ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type), + state->xid); + RT = 0; /* bogus gcc warning */ + } else { + if (state->interval == 0) + state->interval = 4; + else { + state->interval *= 2; + if (state->interval > 64) + state->interval = 64; + } + RT = (state->interval * MSEC_PER_SEC) + + (arc4random_uniform(MSEC_PER_SEC * 2) - MSEC_PER_SEC); + /* No carrier? Don't bother sending the packet. + * However, we do need to advance the timeout. */ + if (!if_is_link_up(ifp)) + goto fail; + logdebugx("%s: sending %s (xid 0x%x), next in %0.1f seconds", + ifp->name, + ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type), + state->xid, + (float)RT / MSEC_PER_SEC); + } + + r = make_message(&bootp, ifp, type); + if (r == -1) + goto fail; + len = (size_t)r; + + if (!(state->added & (STATE_FAKE | STATE_EXPIRED)) && + state->addr != NULL && + ipv4_iffindaddr(ifp, &state->lease.addr, NULL) != NULL) + from.s_addr = state->lease.addr.s_addr; + else + from.s_addr = INADDR_ANY; + if (from.s_addr != INADDR_ANY && + state->lease.server.s_addr != INADDR_ANY) + to.s_addr = state->lease.server.s_addr; + else + to.s_addr = INADDR_BROADCAST; + + /* + * If not listening on the unspecified address we can + * only receive broadcast messages via BPF. + * Sockets bound to an address cannot receive broadcast messages + * even if they are setup to send them. + * Broadcasting from UDP is only an optimisation for rebinding + * and on BSD, at least, is reliant on the subnet route being + * correctly configured to receive the unicast reply. + * As such, we always broadcast and receive the reply to it via BPF. + * This also guarantees we have a DHCP server attached to the + * interface we want to configure because we can't dictate the + * interface via IP_PKTINFO unlike for IPv6. + */ + if (to.s_addr != INADDR_BROADCAST) { + if (dhcp_sendudp(ifp, &to, bootp, len) != -1) + goto out; + logerr("%s: dhcp_sendudp", ifp->name); + } + + if (dhcp_openbpf(ifp) == -1) + goto out; + + udp = dhcp_makeudppacket(&ulen, (uint8_t *)bootp, len, from, to); + if (udp == NULL) { + logerr("%s: dhcp_makeudppacket", ifp->name); + r = 0; +#ifdef PRIVSEP + } else if (ifp->ctx->options & DHCPCD_PRIVSEP) { + r = ps_bpf_sendbootp(ifp, udp, ulen); + free(udp); +#endif + } else { + r = bpf_send(state->bpf, ETHERTYPE_IP, udp, ulen); + free(udp); + } + /* If we failed to send a raw packet this normally means + * we don't have the ability to work beneath the IP layer + * for this interface. + * As such we remove it from consideration without actually + * stopping the interface. */ + if (r == -1) { + logerr("%s: bpf_send", ifp->name); + switch(errno) { + case ENETDOWN: + case ENETRESET: + case ENETUNREACH: + case ENOBUFS: + break; + default: + if (!(ifp->ctx->options & DHCPCD_TEST)) + dhcp_drop(ifp, "FAIL"); + eloop_timeout_delete(ifp->ctx->eloop, + NULL, ifp); + callback = NULL; + } + } + +out: + free(bootp); + +fail: + /* Even if we fail to send a packet we should continue as we are + * as our failure timeouts will change out codepath when needed. */ + if (callback != NULL) + eloop_timeout_add_msec(ifp->ctx->eloop, RT, callback, ifp); +} + +static void +send_inform(void *arg) +{ + + send_message((struct interface *)arg, DHCP_INFORM, send_inform); +} + +static void +send_discover(void *arg) +{ + + send_message((struct interface *)arg, DHCP_DISCOVER, send_discover); +} + +static void +send_request(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_request); +} + +static void +send_renew(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_renew); +} + +static void +send_rebind(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_rebind); +} + +void +dhcp_discover(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct if_options *ifo = ifp->options; + + state->state = DHS_DISCOVER; + dhcp_new_xid(ifp); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + if (!(state->added & STATE_EXPIRED)) { + if (ifo->fallback) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, dhcp_fallback, ifp); +#ifdef IPV4LL + else if (ifo->options & DHCPCD_IPV4LL) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, ipv4ll_start, ifp); +#endif + } + if (ifo->options & DHCPCD_REQUEST) + loginfox("%s: soliciting a DHCP lease (requesting %s)", + ifp->name, inet_ntoa(ifo->req_addr)); + else + loginfox("%s: soliciting a %s lease", + ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : "DHCP"); + send_discover(ifp); +} + +static void +dhcp_request(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + state->state = DHS_REQUEST; + send_request(ifp); +} + +static void +dhcp_expire(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { + logwarnx("%s: DHCP lease expired, extending lease", ifp->name); + state->added |= STATE_EXPIRED; + } else { + logerrx("%s: DHCP lease expired", ifp->name); + dhcp_drop(ifp, "EXPIRE"); + dhcp_unlink(ifp->ctx, state->leasefile); + } + state->interval = 0; + dhcp_discover(ifp); +} + +#if defined(ARP) || defined(IN_IFF_DUPLICATED) +static void +dhcp_decline(struct interface *ifp) +{ + + send_message(ifp, DHCP_DECLINE, NULL); +} +#endif + +static void +dhcp_startrenew(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state; + struct dhcp_lease *lease; + + if ((state = D_STATE(ifp)) == NULL) + return; + + /* Only renew in the bound or renew states */ + if (state->state != DHS_BOUND && + state->state != DHS_RENEW) + return; + + /* Remove the timeout as the renew may have been forced. */ + eloop_timeout_delete(ifp->ctx->eloop, dhcp_startrenew, ifp); + + lease = &state->lease; + logdebugx("%s: renewing lease of %s", ifp->name, + inet_ntoa(lease->addr)); + state->state = DHS_RENEW; + dhcp_new_xid(ifp); + state->interval = 0; + send_renew(ifp); +} + +void +dhcp_renew(struct interface *ifp) +{ + + dhcp_startrenew(ifp); +} + +static void +dhcp_rebind(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct dhcp_lease *lease = &state->lease; + + logwarnx("%s: failed to renew DHCP, rebinding", ifp->name); + logdebugx("%s: expire in %"PRIu32" seconds", + ifp->name, lease->leasetime - lease->rebindtime); + state->state = DHS_REBIND; + eloop_timeout_delete(ifp->ctx->eloop, send_renew, ifp); + state->lease.server.s_addr = INADDR_ANY; + state->interval = 0; + ifp->options->options &= ~(DHCPCD_CSR_WARNED | + DHCPCD_ROUTER_HOST_ROUTE_WARNED); + send_rebind(ifp); +} + +#if defined(ARP) || defined(IN_IFF_DUPLICATED) +static void +dhcp_finish_dad(struct interface *ifp, struct in_addr *ia) +{ + struct dhcp_state *state = D_STATE(ifp); + + if (state->state != DHS_PROBE) + return; + if (state->offer == NULL || state->offer->yiaddr != ia->s_addr) + return; + + logdebugx("%s: DAD completed for %s", ifp->name, inet_ntoa(*ia)); + if (!(ifp->options->options & DHCPCD_INFORM)) + dhcp_bind(ifp); +#ifndef IN_IFF_DUPLICATED + else { + struct bootp *bootp; + size_t len; + + bootp = state->new; + len = state->new_len; + state->new = state->offer; + state->new_len = state->offer_len; + get_lease(ifp, &state->lease, state->new, state->new_len); + ipv4_applyaddr(ifp); + state->new = bootp; + state->new_len = len; + } +#endif + +#ifdef IPV4LL + /* Stop IPv4LL now we have a working DHCP address */ + if (!IN_LINKLOCAL(ntohl(ia->s_addr))) + ipv4ll_drop(ifp); +#endif + + if (ifp->options->options & DHCPCD_INFORM) + dhcp_inform(ifp); +} + +static bool +dhcp_addr_duplicated(struct interface *ifp, struct in_addr *ia) +{ + struct dhcp_state *state = D_STATE(ifp); + unsigned long long opts = ifp->options->options; + struct dhcpcd_ctx *ctx = ifp->ctx; + bool deleted = false; +#ifdef IN_IFF_DUPLICATED + struct ipv4_addr *iap; +#endif + + if ((state->offer == NULL || state->offer->yiaddr != ia->s_addr) && + !IN_ARE_ADDR_EQUAL(ia, &state->lease.addr)) + return deleted; + + /* RFC 2131 3.1.5, Client-server interaction */ + logerrx("%s: DAD detected %s", ifp->name, inet_ntoa(*ia)); + dhcp_unlink(ifp->ctx, state->leasefile); + if (!(opts & DHCPCD_STATIC) && !state->lease.frominfo) + dhcp_decline(ifp); +#ifdef IN_IFF_DUPLICATED + if ((iap = ipv4_iffindaddr(ifp, ia, NULL)) != NULL) { + ipv4_deladdr(iap, 0); + deleted = true; + } +#endif + eloop_timeout_delete(ctx->eloop, NULL, ifp); + if (opts & (DHCPCD_STATIC | DHCPCD_INFORM)) { + state->reason = "EXPIRE"; + script_runreason(ifp, state->reason); +#define NOT_ONLY_SELF (DHCPCD_MANAGER | DHCPCD_IPV6RS | DHCPCD_DHCP6) + if (!(ctx->options & NOT_ONLY_SELF)) + eloop_exit(ifp->ctx->eloop, EXIT_FAILURE); + return deleted; + } + eloop_timeout_add_sec(ifp->ctx->eloop, + DHCP_RAND_MAX, dhcp_discover, ifp); + return deleted; +} +#endif + +#ifdef ARP +#ifdef KERNEL_RFC5227 +#ifdef ARPING +static void +dhcp_arp_announced(struct arp_state *state) +{ + + arp_free(state); +} +#endif +#else +static void +dhcp_arp_defend_failed(struct arp_state *astate) +{ + struct interface *ifp = astate->iface; + + dhcp_drop(ifp, "EXPIRED"); + dhcp_start1(ifp); +} +#endif + +#if !defined(KERNEL_RFC5227) || defined(ARPING) +static void dhcp_arp_not_found(struct arp_state *); + +static struct arp_state * +dhcp_arp_new(struct interface *ifp, struct in_addr *addr) +{ + struct arp_state *astate; + + astate = arp_new(ifp, addr); + if (astate == NULL) + return NULL; + + astate->found_cb = dhcp_arp_found; + astate->not_found_cb = dhcp_arp_not_found; +#ifdef KERNEL_RFC5227 + astate->announced_cb = dhcp_arp_announced; +#else + astate->announced_cb = NULL; + astate->defend_failed_cb = dhcp_arp_defend_failed; +#endif + return astate; +} +#endif + +#ifdef ARPING +static int +dhcp_arping(struct interface *ifp) +{ + struct dhcp_state *state; + struct if_options *ifo; + struct arp_state *astate; + struct in_addr addr; + + state = D_STATE(ifp); + ifo = ifp->options; + + if (ifo->arping_len == 0 || state->arping_index > ifo->arping_len) + return 0; + + if (state->arping_index + 1 == ifo->arping_len) { + state->arping_index++; + dhcpcd_startinterface(ifp); + return 1; + } + + addr.s_addr = ifo->arping[++state->arping_index]; + astate = dhcp_arp_new(ifp, &addr); + if (astate == NULL) { + logerr(__func__); + return -1; + } + arp_probe(astate); + return 1; +} +#endif + +#if !defined(KERNEL_RFC5227) || defined(ARPING) +static void +dhcp_arp_not_found(struct arp_state *astate) +{ + struct interface *ifp; + + ifp = astate->iface; +#ifdef ARPING + if (dhcp_arping(ifp) == 1) { + arp_free(astate); + return; + } +#endif + + dhcp_finish_dad(ifp, &astate->addr); +} + +static void +dhcp_arp_found(struct arp_state *astate, const struct arp_msg *amsg) +{ + struct in_addr addr; + struct interface *ifp = astate->iface; +#ifdef ARPING + struct dhcp_state *state; + struct if_options *ifo; + + state = D_STATE(ifp); + + ifo = ifp->options; + if (state->arping_index != -1 && + state->arping_index < ifo->arping_len && + amsg && + amsg->sip.s_addr == ifo->arping[state->arping_index]) + { + char buf[HWADDR_LEN * 3]; + + hwaddr_ntoa(amsg->sha, ifp->hwlen, buf, sizeof(buf)); + if (dhcpcd_selectprofile(ifp, buf) == -1 && + dhcpcd_selectprofile(ifp, inet_ntoa(amsg->sip)) == -1) + { + /* We didn't find a profile for this + * address or hwaddr, so move to the next + * arping profile */ + dhcp_arp_not_found(astate); + return; + } + arp_free(astate); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + dhcpcd_startinterface(ifp); + return; + } +#else + UNUSED(amsg); +#endif + + addr = astate->addr; + arp_free(astate); + dhcp_addr_duplicated(ifp, &addr); +} +#endif + +#endif /* ARP */ + +void +dhcp_bind(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct dhcp_state *state = D_STATE(ifp); + struct if_options *ifo = ifp->options; + struct dhcp_lease *lease = &state->lease; + uint8_t old_state; + + state->reason = NULL; + /* If we don't have an offer, we are re-binding a lease on preference, + * normally when two interfaces have a lease matching IP addresses. */ + if (state->offer) { + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = state->offer; + state->new_len = state->offer_len; + state->offer = NULL; + state->offer_len = 0; + } + get_lease(ifp, lease, state->new, state->new_len); + if (ifo->options & DHCPCD_STATIC) { + loginfox("%s: using static address %s/%d", + ifp->name, inet_ntoa(lease->addr), + inet_ntocidr(lease->mask)); + lease->leasetime = DHCP_INFINITE_LIFETIME; + state->reason = "STATIC"; + } else if (ifo->options & DHCPCD_INFORM) { + loginfox("%s: received approval for %s", + ifp->name, inet_ntoa(lease->addr)); + lease->leasetime = DHCP_INFINITE_LIFETIME; + state->reason = "INFORM"; + } else { + if (lease->frominfo) + state->reason = "TIMEOUT"; + if (lease->leasetime == DHCP_INFINITE_LIFETIME) { + lease->renewaltime = + lease->rebindtime = + lease->leasetime; + loginfox("%s: leased %s for infinity", + ifp->name, inet_ntoa(lease->addr)); + } else { + if (lease->leasetime < DHCP_MIN_LEASE) { + logwarnx("%s: minimum lease is %d seconds", + ifp->name, DHCP_MIN_LEASE); + lease->leasetime = DHCP_MIN_LEASE; + } + if (lease->rebindtime == 0) + lease->rebindtime = + (uint32_t)(lease->leasetime * T2); + else if (lease->rebindtime >= lease->leasetime) { + lease->rebindtime = + (uint32_t)(lease->leasetime * T2); + logwarnx("%s: rebind time greater than lease " + "time, forcing to %"PRIu32" seconds", + ifp->name, lease->rebindtime); + } + if (lease->renewaltime == 0) + lease->renewaltime = + (uint32_t)(lease->leasetime * T1); + else if (lease->renewaltime > lease->rebindtime) { + lease->renewaltime = + (uint32_t)(lease->leasetime * T1); + logwarnx("%s: renewal time greater than " + "rebind time, forcing to %"PRIu32" seconds", + ifp->name, lease->renewaltime); + } + if (state->state == DHS_RENEW && state->addr && + lease->addr.s_addr == state->addr->addr.s_addr && + !(state->added & STATE_FAKE)) + logdebugx("%s: leased %s for %"PRIu32" seconds", + ifp->name, inet_ntoa(lease->addr), + lease->leasetime); + else + loginfox("%s: leased %s for %"PRIu32" seconds", + ifp->name, inet_ntoa(lease->addr), + lease->leasetime); + } + } + if (ctx->options & DHCPCD_TEST) { + state->reason = "TEST"; + script_runreason(ifp, state->reason); + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + if (state->reason == NULL) { + if (state->old && + !(state->added & (STATE_FAKE | STATE_EXPIRED))) + { + if (state->old->yiaddr == state->new->yiaddr && + lease->server.s_addr && + state->state != DHS_REBIND) + state->reason = "RENEW"; + else + state->reason = "REBIND"; + } else if (state->state == DHS_REBOOT) + state->reason = "REBOOT"; + else + state->reason = "BOUND"; + } + if (lease->leasetime == DHCP_INFINITE_LIFETIME) + lease->renewaltime = lease->rebindtime = lease->leasetime; + else { + eloop_timeout_add_sec(ctx->eloop, + lease->renewaltime, dhcp_startrenew, ifp); + eloop_timeout_add_sec(ctx->eloop, + lease->rebindtime, dhcp_rebind, ifp); + eloop_timeout_add_sec(ctx->eloop, + lease->leasetime, dhcp_expire, ifp); + logdebugx("%s: renew in %"PRIu32" seconds, rebind in %"PRIu32 + " seconds", + ifp->name, lease->renewaltime, lease->rebindtime); + } + state->state = DHS_BOUND; + if (!state->lease.frominfo && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) { + logdebugx("%s: writing lease: %s", + ifp->name, state->leasefile); + if (dhcp_writefile(ifp->ctx, state->leasefile, 0640, + state->new, state->new_len) == -1) + logerr("dhcp_writefile: %s", state->leasefile); + } + + old_state = state->added; + + if (!(ifo->options & DHCPCD_CONFIGURE)) { + struct ipv4_addr *ia; + + script_runreason(ifp, state->reason); + dhcpcd_daemonise(ifp->ctx); + + /* We we are not configuring the address, we need to keep + * the BPF socket open if the address does not exist. */ + ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL); + if (ia != NULL) { + state->addr = ia; + state->added = STATE_ADDED; + dhcp_closebpf(ifp); + goto openudp; + } + return; + } + + /* Add the address */ + if (ipv4_applyaddr(ifp) == NULL) { + /* There was an error adding the address. + * If we are in oneshot, exit with a failure. */ + if (ctx->options & DHCPCD_ONESHOT) { + loginfox("exiting due to oneshot"); + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + return; + } + + /* Close the BPF filter as we can now receive DHCP messages + * on a UDP socket. */ + dhcp_closebpf(ifp); + +openudp: + /* If not in manager mode, open an address specific socket. */ + if (ctx->options & DHCPCD_MANAGER || + ifo->options & DHCPCD_STATIC || + (state->old != NULL && + state->old->yiaddr == state->new->yiaddr && + old_state & STATE_ADDED && !(old_state & STATE_FAKE))) + return; + + dhcp_closeinet(ifp); +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) { + if (ps_inet_openbootp(state->addr) == -1) + logerr(__func__); + return; + } +#endif + + state->udp_rfd = dhcp_openudp(&state->addr->addr); + if (state->udp_rfd == -1) { + logerr(__func__); + /* Address sharing without manager mode is not supported. + * It's also possible another DHCP client could be running, + * which is even worse. + * We still need to work, so re-open BPF. */ + dhcp_openbpf(ifp); + return; + } + eloop_event_add(ctx->eloop, state->udp_rfd, dhcp_handleifudp, ifp); +} + +static size_t +dhcp_message_new(struct bootp **bootp, + const struct in_addr *addr, const struct in_addr *mask) +{ + uint8_t *p; + uint32_t cookie; + + if ((*bootp = calloc(1, sizeof(**bootp))) == NULL) + return 0; + + (*bootp)->yiaddr = addr->s_addr; + p = (*bootp)->vend; + + cookie = htonl(MAGIC_COOKIE); + memcpy(p, &cookie, sizeof(cookie)); + p += sizeof(cookie); + + if (mask->s_addr != INADDR_ANY) { + *p++ = DHO_SUBNETMASK; + *p++ = sizeof(mask->s_addr); + memcpy(p, &mask->s_addr, sizeof(mask->s_addr)); + p+= sizeof(mask->s_addr); + } + + *p = DHO_END; + return sizeof(**bootp); +} + +#if defined(ARP) || defined(KERNEL_RFC5227) +static int +dhcp_arp_address(struct interface *ifp) +{ + struct dhcp_state *state; + struct in_addr addr; + struct ipv4_addr *ia; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + + state = D_STATE(ifp); + addr.s_addr = state->offer->yiaddr == INADDR_ANY ? + state->offer->ciaddr : state->offer->yiaddr; + /* If the interface already has the address configured + * then we can't ARP for duplicate detection. */ + ia = ipv4_iffindaddr(ifp, &addr, NULL); +#ifdef IN_IFF_NOTUSEABLE + if (ia == NULL || ia->addr_flags & IN_IFF_NOTUSEABLE) { + state->state = DHS_PROBE; + if (ia == NULL) { + struct dhcp_lease l; + + get_lease(ifp, &l, state->offer, state->offer_len); + /* Add the address now, let the kernel handle DAD. */ + ipv4_addaddr(ifp, &l.addr, &l.mask, &l.brd, + l.leasetime, l.rebindtime); + } else if (ia->addr_flags & IN_IFF_DUPLICATED) + dhcp_addr_duplicated(ifp, &ia->addr); + else + loginfox("%s: waiting for DAD on %s", + ifp->name, inet_ntoa(addr)); + return 0; + } +#else + if (!(ifp->flags & IFF_NOARP) && + ifp->options->options & DHCPCD_ARP) + { + struct arp_state *astate; + struct dhcp_lease l; + + /* Even if the address exists, we need to defend it. */ + astate = dhcp_arp_new(ifp, &addr); + if (astate == NULL) + return -1; + + if (ia == NULL) { + state->state = DHS_PROBE; + get_lease(ifp, &l, state->offer, state->offer_len); + loginfox("%s: probing address %s/%d", + ifp->name, inet_ntoa(l.addr), inet_ntocidr(l.mask)); + /* We need to handle DAD. */ + arp_probe(astate); + return 0; + } + } +#endif + + return 1; +} + +static void +dhcp_arp_bind(struct interface *ifp) +{ + + if (ifp->ctx->options & DHCPCD_TEST || + dhcp_arp_address(ifp) == 1) + dhcp_bind(ifp); +} +#endif + +static void +dhcp_lastlease(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + loginfox("%s: timed out contacting a DHCP server, using last lease", + ifp->name); +#if defined(ARP) || defined(KERNEL_RFC5227) + dhcp_arp_bind(ifp); +#else + dhcp_bind(ifp); +#endif + /* Set expired here because dhcp_bind() -> ipv4_addaddr() will reset + * state */ + state->added |= STATE_EXPIRED; + state->interval = 0; + dhcp_discover(ifp); +} + +static void +dhcp_static(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp_state *state; + struct ipv4_addr *ia; + + state = D_STATE(ifp); + ifo = ifp->options; + + ia = NULL; + if (ifo->req_addr.s_addr == INADDR_ANY && + (ia = ipv4_iffindaddr(ifp, NULL, NULL)) == NULL) + { + loginfox("%s: waiting for 3rd party to " + "configure IP address", ifp->name); + state->reason = "3RDPARTY"; + script_runreason(ifp, state->reason); + return; + } + + state->offer_len = dhcp_message_new(&state->offer, + ia ? &ia->addr : &ifo->req_addr, + ia ? &ia->mask : &ifo->req_mask); + if (state->offer_len) +#if defined(ARP) || defined(KERNEL_RFC5227) + dhcp_arp_bind(ifp); +#else + dhcp_bind(ifp); +#endif +} + +void +dhcp_inform(struct interface *ifp) +{ + struct dhcp_state *state; + struct if_options *ifo; + struct ipv4_addr *ia; + + state = D_STATE(ifp); + ifo = ifp->options; + + free(state->offer); + state->offer = NULL; + state->offer_len = 0; + + if (ifo->req_addr.s_addr == INADDR_ANY) { + ia = ipv4_iffindaddr(ifp, NULL, NULL); + if (ia == NULL) { + loginfox("%s: waiting for 3rd party to " + "configure IP address", + ifp->name); + if (!(ifp->ctx->options & DHCPCD_TEST)) { + state->reason = "3RDPARTY"; + script_runreason(ifp, state->reason); + } + return; + } + } else { + ia = ipv4_iffindaddr(ifp, &ifo->req_addr, &ifo->req_mask); + if (ia == NULL) { + if (ifp->ctx->options & DHCPCD_TEST) { + logerrx("%s: cannot add IP address in test mode", + ifp->name); + return; + } + ia = ipv4_iffindaddr(ifp, &ifo->req_addr, NULL); + if (ia != NULL) + /* Netmask must be different, delete it. */ + ipv4_deladdr(ia, 1); + state->offer_len = dhcp_message_new(&state->offer, + &ifo->req_addr, &ifo->req_mask); +#ifdef ARP + if (dhcp_arp_address(ifp) != 1) + return; +#endif + ia = ipv4_iffindaddr(ifp, + &ifo->req_addr, &ifo->req_mask); + assert(ia != NULL); + } + } + + state->state = DHS_INFORM; + state->addr = ia; + state->offer_len = dhcp_message_new(&state->offer, + &ia->addr, &ia->mask); + if (state->offer_len) { + dhcp_new_xid(ifp); + get_lease(ifp, &state->lease, state->offer, state->offer_len); + send_inform(ifp); + } +} + +void +dhcp_reboot_newopts(struct interface *ifp, unsigned long long oldopts) +{ + struct if_options *ifo; + struct dhcp_state *state = D_STATE(ifp); + + if (state == NULL || state->state == DHS_NONE) + return; + ifo = ifp->options; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) && + (state->addr == NULL || + state->addr->addr.s_addr != ifo->req_addr.s_addr)) || + (oldopts & (DHCPCD_INFORM | DHCPCD_STATIC) && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))) + { + dhcp_drop(ifp, "EXPIRE"); + } +} + +#ifdef ARP +static int +dhcp_activeaddr(const struct interface *ifp, const struct in_addr *addr) +{ + const struct interface *ifp1; + const struct dhcp_state *state; + + TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) { + if (ifp1 == ifp) + continue; + if ((state = D_CSTATE(ifp1)) == NULL) + continue; + switch(state->state) { + case DHS_REBOOT: + case DHS_RENEW: + case DHS_REBIND: + case DHS_BOUND: + case DHS_INFORM: + break; + default: + continue; + } + if (state->lease.addr.s_addr == addr->s_addr) + return 1; + } + return 0; +} +#endif + +static void +dhcp_reboot(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp_state *state = D_STATE(ifp); +#ifdef ARP + struct ipv4_addr *ia; +#endif + + if (state == NULL || state->state == DHS_NONE) + return; + ifo = ifp->options; + state->state = DHS_REBOOT; + state->interval = 0; + + if (ifo->options & DHCPCD_LINK && !if_is_link_up(ifp)) { + loginfox("%s: waiting for carrier", ifp->name); + return; + } + if (ifo->options & DHCPCD_STATIC) { + dhcp_static(ifp); + return; + } + if (ifo->options & DHCPCD_INFORM) { + loginfox("%s: informing address of %s", + ifp->name, inet_ntoa(state->lease.addr)); + dhcp_inform(ifp); + return; + } + if (ifo->reboot == 0 || state->offer == NULL) { + dhcp_discover(ifp); + return; + } + if (!IS_DHCP(state->offer)) + return; + + loginfox("%s: rebinding lease of %s", + ifp->name, inet_ntoa(state->lease.addr)); + +#ifdef ARP +#ifndef KERNEL_RFC5227 + /* Create the DHCP ARP state so we can defend it. */ + (void)dhcp_arp_new(ifp, &state->lease.addr); +#endif + + /* If the address exists on the interface and no other interface + * is currently using it then announce it to ensure this + * interface gets the reply. */ + ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL); + if (ia != NULL && + !(ifp->ctx->options & DHCPCD_TEST) && +#ifdef IN_IFF_NOTUSEABLE + !(ia->addr_flags & IN_IFF_NOTUSEABLE) && +#endif + dhcp_activeaddr(ifp, &state->lease.addr) == 0) + arp_ifannounceaddr(ifp, &state->lease.addr); +#endif + + dhcp_new_xid(ifp); + state->lease.server.s_addr = INADDR_ANY; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + +#ifdef IPV4LL + /* Need to add this before dhcp_expire and friends. */ + if (!ifo->fallback && ifo->options & DHCPCD_IPV4LL) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, ipv4ll_start, ifp); +#endif + + if (ifo->options & DHCPCD_LASTLEASE && state->lease.frominfo) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, dhcp_lastlease, ifp); + else if (!(ifo->options & DHCPCD_INFORM)) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, dhcp_expire, ifp); + + /* Don't bother ARP checking as the server could NAK us first. + * Don't call dhcp_request as that would change the state */ + send_request(ifp); +} + +void +dhcp_drop(struct interface *ifp, const char *reason) +{ + struct dhcp_state *state; +#ifdef RELEASE_SLOW + struct timespec ts; +#endif + + state = D_STATE(ifp); + /* dhcp_start may just have been called and we don't yet have a state + * but we do have a timeout, so punt it. */ + if (state == NULL || state->state == DHS_NONE) { + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + return; + } + +#ifdef ARP + if (state->addr != NULL) + arp_freeaddr(ifp, &state->addr->addr); +#endif +#ifdef ARPING + state->arping_index = -1; +#endif + + if (ifp->options->options & DHCPCD_RELEASE && + !(ifp->options->options & DHCPCD_INFORM)) + { + /* Failure to send the release may cause this function to + * re-enter so guard by setting the state. */ + if (state->state == DHS_RELEASE) + return; + state->state = DHS_RELEASE; + + dhcp_unlink(ifp->ctx, state->leasefile); + if (if_is_link_up(ifp) && + state->new != NULL && + state->lease.server.s_addr != INADDR_ANY) + { + loginfox("%s: releasing lease of %s", + ifp->name, inet_ntoa(state->lease.addr)); + dhcp_new_xid(ifp); + send_message(ifp, DHCP_RELEASE, NULL); +#ifdef RELEASE_SLOW + /* Give the packet a chance to go */ + ts.tv_sec = RELEASE_DELAY_S; + ts.tv_nsec = RELEASE_DELAY_NS; + nanosleep(&ts, NULL); +#endif + } + } +#ifdef AUTH + else if (state->auth.reconf != NULL) { + /* + * Drop the lease as the token may only be present + * in the initial reply message and not subsequent + * renewals. + * If dhcpcd is restarted, the token is lost. + * XXX persist this in another file? + */ + dhcp_unlink(ifp->ctx, state->leasefile); + } +#endif + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); +#ifdef AUTH + dhcp_auth_reset(&state->auth); +#endif + + /* Close DHCP ports so a changed interface family is picked + * up by a new BPF state. */ + dhcp_close(ifp); + + state->state = DHS_NONE; + free(state->offer); + state->offer = NULL; + state->offer_len = 0; + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = NULL; + state->new_len = 0; + state->reason = reason; + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_applyaddr(ifp); + else { + state->addr = NULL; + state->added = 0; + script_runreason(ifp, state->reason); + } + free(state->old); + state->old = NULL; + state->old_len = 0; + state->lease.addr.s_addr = 0; + ifp->options->options &= ~(DHCPCD_CSR_WARNED | + DHCPCD_ROUTER_HOST_ROUTE_WARNED); +} + +static int +blacklisted_ip(const struct if_options *ifo, in_addr_t addr) +{ + size_t i; + + for (i = 0; i < ifo->blacklist_len; i += 2) + if (ifo->blacklist[i] == (addr & ifo->blacklist[i + 1])) + return 1; + return 0; +} + +#define WHTLST_NONE 0 +#define WHTLST_MATCH 1 +#define WHTLST_NOMATCH 2 +static unsigned int +whitelisted_ip(const struct if_options *ifo, in_addr_t addr) +{ + size_t i; + + if (ifo->whitelist_len == 0) + return WHTLST_NONE; + for (i = 0; i < ifo->whitelist_len; i += 2) + if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1])) + return WHTLST_MATCH; + return WHTLST_NOMATCH; +} + +static void +log_dhcp(int loglevel, const char *msg, + const struct interface *ifp, const struct bootp *bootp, size_t bootp_len, + const struct in_addr *from, int ad) +{ + const char *tfrom; + char *a, sname[sizeof(bootp->sname) * 4]; + struct in_addr addr; + int r; + uint8_t overl; + + if (strcmp(msg, "NAK:") == 0) { + a = get_option_string(ifp->ctx, bootp, bootp_len, DHO_MESSAGE); + if (a) { + char *tmp; + size_t al, tmpl; + + al = strlen(a); + tmpl = (al * 4) + 1; + tmp = malloc(tmpl); + if (tmp == NULL) { + logerr(__func__); + free(a); + return; + } + print_string(tmp, tmpl, OT_STRING, (uint8_t *)a, al); + free(a); + a = tmp; + } + } else if (ad && bootp->yiaddr != 0) { + addr.s_addr = bootp->yiaddr; + a = strdup(inet_ntoa(addr)); + if (a == NULL) { + logerr(__func__); + return; + } + } else + a = NULL; + + tfrom = "from"; + r = get_option_addr(ifp->ctx, &addr, bootp, bootp_len, DHO_SERVERID); + if (get_option_uint8(ifp->ctx, &overl, bootp, bootp_len, + DHO_OPTSOVERLOADED) == -1) + overl = 0; + if (bootp->sname[0] && r == 0 && !(overl & 2)) { + print_string(sname, sizeof(sname), OT_STRING | OT_DOMAIN, + bootp->sname, sizeof(bootp->sname)); + if (a == NULL) + logmessage(loglevel, "%s: %s %s %s %s", + ifp->name, msg, tfrom, inet_ntoa(addr), sname); + else + logmessage(loglevel, "%s: %s %s %s %s %s", + ifp->name, msg, a, tfrom, inet_ntoa(addr), sname); + } else { + if (r != 0) { + tfrom = "via"; + addr = *from; + } + if (a == NULL) + logmessage(loglevel, "%s: %s %s %s", + ifp->name, msg, tfrom, inet_ntoa(addr)); + else + logmessage(loglevel, "%s: %s %s %s %s", + ifp->name, msg, a, tfrom, inet_ntoa(addr)); + } + free(a); +} + +/* If we're sharing the same IP address with another interface on the + * same network, we may receive the DHCP reply on the wrong interface. + * Try and re-direct it here. */ +static void +dhcp_redirect_dhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, + const struct in_addr *from) +{ + struct interface *ifn; + const struct dhcp_state *state; + uint32_t xid; + + xid = ntohl(bootp->xid); + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ifn == ifp) + continue; + state = D_CSTATE(ifn); + if (state == NULL || state->state == DHS_NONE) + continue; + if (state->xid != xid) + continue; + if (ifn->hwlen <= sizeof(bootp->chaddr) && + memcmp(bootp->chaddr, ifn->hwaddr, ifn->hwlen)) + continue; + logdebugx("%s: redirecting DHCP message to %s", + ifp->name, ifn->name); + dhcp_handledhcp(ifn, bootp, bootp_len, from); + } +} + +static void +dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len, + const struct in_addr *from) +{ + struct dhcp_state *state = D_STATE(ifp); + struct if_options *ifo = ifp->options; + struct dhcp_lease *lease = &state->lease; + uint8_t type, tmp; + struct in_addr addr; + unsigned int i; + char *msg; + bool bootp_copied; + uint32_t v6only_time = 0; + bool use_v6only = false; +#ifdef AUTH + const uint8_t *auth; + size_t auth_len; +#endif +#ifdef IN_IFF_DUPLICATED + struct ipv4_addr *ia; +#endif + +#define LOGDHCP0(l, m) \ + log_dhcp((l), (m), ifp, bootp, bootp_len, from, 0) +#define LOGDHCP(l, m) \ + log_dhcp((l), (m), ifp, bootp, bootp_len, from, 1) + +#define IS_STATE_ACTIVE(s) ((s)-state != DHS_NONE && \ + (s)->state != DHS_INIT && (s)->state != DHS_BOUND) + + if (bootp->op != BOOTREPLY) { + if (IS_STATE_ACTIVE(state)) + logdebugx("%s: op (%d) is not BOOTREPLY", + ifp->name, bootp->op); + return; + } + + if (state->xid != ntohl(bootp->xid)) { + if (IS_STATE_ACTIVE(state)) + logdebugx("%s: wrong xid 0x%x (expecting 0x%x) from %s", + ifp->name, ntohl(bootp->xid), state->xid, + inet_ntoa(*from)); + dhcp_redirect_dhcp(ifp, bootp, bootp_len, from); + return; + } + + if (ifp->hwlen <= sizeof(bootp->chaddr) && + memcmp(bootp->chaddr, ifp->hwaddr, ifp->hwlen)) + { + if (IS_STATE_ACTIVE(state)) { + char buf[sizeof(bootp->chaddr) * 3]; + + logdebugx("%s: xid 0x%x is for hwaddr %s", + ifp->name, ntohl(bootp->xid), + hwaddr_ntoa(bootp->chaddr, sizeof(bootp->chaddr), + buf, sizeof(buf))); + } + dhcp_redirect_dhcp(ifp, bootp, bootp_len, from); + return; + } + + if (!ifp->active) + return; + + i = whitelisted_ip(ifp->options, from->s_addr); + switch (i) { + case WHTLST_NOMATCH: + logwarnx("%s: non whitelisted DHCP packet from %s", + ifp->name, inet_ntoa(*from)); + return; + case WHTLST_MATCH: + break; + case WHTLST_NONE: + if (blacklisted_ip(ifp->options, from->s_addr) == 1) { + logwarnx("%s: blacklisted DHCP packet from %s", + ifp->name, inet_ntoa(*from)); + return; + } + } + + /* We may have found a BOOTP server */ + if (get_option_uint8(ifp->ctx, &type, + bootp, bootp_len, DHO_MESSAGETYPE) == -1) + type = 0; + else if (ifo->options & DHCPCD_BOOTP) { + logdebugx("%s: ignoring DHCP reply (expecting BOOTP)", + ifp->name); + return; + } + +#ifdef AUTH + /* Authenticate the message */ + auth = get_option(ifp->ctx, bootp, bootp_len, + DHO_AUTHENTICATION, &auth_len); + if (auth) { + if (dhcp_auth_validate(&state->auth, &ifo->auth, + (uint8_t *)bootp, bootp_len, 4, type, + auth, auth_len) == NULL) + { + LOGDHCP0(LOG_ERR, "authentication failed"); + return; + } + if (state->auth.token) + logdebugx("%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + else + loginfox("%s: accepted reconfigure key", ifp->name); + } else if (ifo->auth.options & DHCPCD_AUTH_SEND) { + if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) { + LOGDHCP0(LOG_ERR, "no authentication"); + return; + } + LOGDHCP0(LOG_WARNING, "no authentication"); + } +#endif + + /* RFC 3203 */ + if (type == DHCP_FORCERENEW) { + if (from->s_addr == INADDR_ANY || + from->s_addr == INADDR_BROADCAST) + { + LOGDHCP(LOG_ERR, "discarding Force Renew"); + return; + } +#ifdef AUTH + if (auth == NULL) { + LOGDHCP(LOG_ERR, "unauthenticated Force Renew"); + if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) + return; + } + if (state->state != DHS_BOUND && state->state != DHS_INFORM) { + LOGDHCP(LOG_DEBUG, "not bound, ignoring Force Renew"); + return; + } + LOGDHCP(LOG_INFO, "Force Renew from"); + /* The rebind and expire timings are still the same, we just + * enter the renew state early */ + if (state->state == DHS_BOUND) + dhcp_renew(ifp); + else { + eloop_timeout_delete(ifp->ctx->eloop, + send_inform, ifp); + dhcp_inform(ifp); + } +#else + LOGDHCP(LOG_ERR, "unauthenticated Force Renew"); +#endif + return; + } + + if (state->state == DHS_BOUND) { + LOGDHCP(LOG_DEBUG, "bound, ignoring"); + return; + } + + if (state->state == DHS_PROBE) { + /* Ignore any DHCP messages whilst probing a lease to bind. */ + LOGDHCP(LOG_DEBUG, "probing, ignoring"); + return; + } + + /* reset the message counter */ + state->interval = 0; + + /* Ensure that no reject options are present */ + for (i = 1; i < 255; i++) { + if (has_option_mask(ifo->rejectmask, i) && + get_option_uint8(ifp->ctx, &tmp, + bootp, bootp_len, (uint8_t)i) == 0) + { + LOGDHCP(LOG_WARNING, "reject DHCP"); + return; + } + } + + if (type == DHCP_NAK) { + /* For NAK, only check if we require the ServerID */ + if (has_option_mask(ifo->requiremask, DHO_SERVERID) && + get_option_addr(ifp->ctx, &addr, + bootp, bootp_len, DHO_SERVERID) == -1) + { + LOGDHCP(LOG_WARNING, "reject NAK"); + return; + } + + /* We should restart on a NAK */ + LOGDHCP(LOG_WARNING, "NAK:"); + if ((msg = get_option_string(ifp->ctx, + bootp, bootp_len, DHO_MESSAGE))) + { + logwarnx("%s: message: %s", ifp->name, msg); + free(msg); + } + if (state->state == DHS_INFORM) /* INFORM should not be NAKed */ + return; + if (!(ifp->ctx->options & DHCPCD_TEST)) { + dhcp_drop(ifp, "NAK"); + dhcp_unlink(ifp->ctx, state->leasefile); + } + + /* If we constantly get NAKS then we should slowly back off */ + eloop_timeout_add_sec(ifp->ctx->eloop, + state->nakoff, dhcp_discover, ifp); + if (state->nakoff == 0) + state->nakoff = 1; + else { + state->nakoff *= 2; + if (state->nakoff > NAKOFF_MAX) + state->nakoff = NAKOFF_MAX; + } + return; + } + + /* Ensure that all required options are present */ + for (i = 1; i < 255; i++) { + if (has_option_mask(ifo->requiremask, i) && + get_option_uint8(ifp->ctx, &tmp, + bootp, bootp_len, (uint8_t)i) != 0) + { + /* If we are BOOTP, then ignore the need for serverid. + * To ignore BOOTP, require dhcp_message_type. + * However, nothing really stops BOOTP from providing + * DHCP style options as well so the above isn't + * always true. */ + if (type == 0 && i == DHO_SERVERID) + continue; + LOGDHCP(LOG_WARNING, "reject DHCP"); + return; + } + } + + if (has_option_mask(ifo->requestmask, DHO_IPV6_PREFERRED_ONLY)) { + if (get_option_uint32(ifp->ctx, &v6only_time, bootp, bootp_len, + DHO_IPV6_PREFERRED_ONLY) == 0 && + (state->state == DHS_DISCOVER || state->state == DHS_REBOOT)) + { + char v6msg[128]; + + use_v6only = true; + if (v6only_time < MIN_V6ONLY_WAIT) + v6only_time = MIN_V6ONLY_WAIT; + snprintf(v6msg, sizeof(v6msg), + "IPv6-Only Preferred received (%u seconds)", + v6only_time); + LOGDHCP(LOG_INFO, v6msg); + } + } + + /* DHCP Auto-Configure, RFC 2563 */ + if (type == DHCP_OFFER && bootp->yiaddr == 0) { + LOGDHCP(LOG_WARNING, "no address given"); + if ((msg = get_option_string(ifp->ctx, + bootp, bootp_len, DHO_MESSAGE))) + { + logwarnx("%s: message: %s", ifp->name, msg); + free(msg); + } +#ifdef IPV4LL + if (state->state == DHS_DISCOVER && + get_option_uint8(ifp->ctx, &tmp, bootp, bootp_len, + DHO_AUTOCONFIGURE) == 0) + { + switch (tmp) { + case 0: + LOGDHCP(LOG_WARNING, "IPv4LL disabled from"); + ipv4ll_drop(ifp); +#ifdef ARP + arp_drop(ifp); +#endif + break; + case 1: + LOGDHCP(LOG_WARNING, "IPv4LL enabled from"); + ipv4ll_start(ifp); + break; + default: + logerrx("%s: unknown auto configuration " + "option %d", + ifp->name, tmp); + break; + } + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, + use_v6only ? v6only_time : DHCP_MAX, + dhcp_discover, ifp); + } +#endif + return; + } + + if (use_v6only) { + dhcp_drop(ifp, "EXPIRE"); + dhcp_unlink(ifp->ctx, state->leasefile); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, v6only_time, + dhcp_discover, ifp); + return; + } + + /* Ensure that the address offered is valid */ + if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) && + (bootp->ciaddr == INADDR_ANY || bootp->ciaddr == INADDR_BROADCAST) + && + (bootp->yiaddr == INADDR_ANY || bootp->yiaddr == INADDR_BROADCAST)) + { + LOGDHCP(LOG_WARNING, "reject invalid address"); + return; + } + +#ifdef IN_IFF_DUPLICATED + ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); + if (ia && ia->addr_flags & IN_IFF_DUPLICATED) { + LOGDHCP(LOG_WARNING, "declined duplicate address"); + if (type) + dhcp_decline(ifp); + ipv4_deladdr(ia, 0); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, + DHCP_RAND_MAX, dhcp_discover, ifp); + return; + } +#endif + + bootp_copied = false; + if ((type == 0 || type == DHCP_OFFER) && state->state == DHS_DISCOVER) { + lease->frominfo = 0; + lease->addr.s_addr = bootp->yiaddr; + memcpy(&lease->cookie, bootp->vend, sizeof(lease->cookie)); + if (type == 0 || + get_option_addr(ifp->ctx, + &lease->server, bootp, bootp_len, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; + + /* Test for rapid commit in the OFFER */ + if (!(ifp->ctx->options & DHCPCD_TEST) && + has_option_mask(ifo->requestmask, DHO_RAPIDCOMMIT) && + get_option(ifp->ctx, bootp, bootp_len, + DHO_RAPIDCOMMIT, NULL)) + { + state->state = DHS_REQUEST; + goto rapidcommit; + } + + LOGDHCP(LOG_INFO, "offered"); + if (state->offer_len < bootp_len) { + free(state->offer); + if ((state->offer = malloc(bootp_len)) == NULL) { + logerr(__func__); + state->offer_len = 0; + return; + } + } + state->offer_len = bootp_len; + memcpy(state->offer, bootp, bootp_len); + bootp_copied = true; + if (ifp->ctx->options & DHCPCD_TEST) { + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = state->offer; + state->new_len = state->offer_len; + state->offer = NULL; + state->offer_len = 0; + state->reason = "TEST"; + script_runreason(ifp, state->reason); + eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); + state->bpf->bpf_flags |= BPF_EOF; + return; + } + eloop_timeout_delete(ifp->ctx->eloop, send_discover, ifp); + /* We don't request BOOTP addresses */ + if (type) { + /* We used to ARP check here, but that seems to be in + * violation of RFC2131 where it only describes + * DECLINE after REQUEST. + * It also seems that some MS DHCP servers actually + * ignore DECLINE if no REQUEST, ie we decline a + * DISCOVER. */ + dhcp_request(ifp); + return; + } + } + + if (type) { + if (type == DHCP_OFFER) { + LOGDHCP(LOG_WARNING, "ignoring offer of"); + return; + } + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + LOGDHCP(LOG_ERR, "not ACK or OFFER"); + return; + } + + if (state->state == DHS_DISCOVER) { + /* We only allow ACK of rapid commit DISCOVER. */ + if (has_option_mask(ifo->requestmask, + DHO_RAPIDCOMMIT) && + get_option(ifp->ctx, bootp, bootp_len, + DHO_RAPIDCOMMIT, NULL)) + state->state = DHS_REQUEST; + else { + LOGDHCP(LOG_DEBUG, "ignoring ack of"); + return; + } + } + +rapidcommit: + if (!(ifo->options & DHCPCD_INFORM)) + LOGDHCP(LOG_DEBUG, "acknowledged"); + else + ifo->options &= ~DHCPCD_STATIC; + } + + /* No NAK, so reset the backoff + * We don't reset on an OFFER message because the server could + * potentially NAK the REQUEST. */ + state->nakoff = 0; + + /* BOOTP could have already assigned this above. */ + if (!bootp_copied) { + if (state->offer_len < bootp_len) { + free(state->offer); + if ((state->offer = malloc(bootp_len)) == NULL) { + logerr(__func__); + state->offer_len = 0; + return; + } + } + state->offer_len = bootp_len; + memcpy(state->offer, bootp, bootp_len); + } + + lease->frominfo = 0; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + +#if defined(ARP) || defined(KERNEL_RFC5227) + dhcp_arp_bind(ifp); +#else + dhcp_bind(ifp); +#endif +} + +static void * +get_udp_data(void *packet, size_t *len) +{ + const struct ip *ip = packet; + size_t ip_hl = (size_t)ip->ip_hl * 4; + char *p = packet; + + p += ip_hl + sizeof(struct udphdr); + *len = (size_t)ntohs(ip->ip_len) - sizeof(struct udphdr) - ip_hl; + return p; +} + +static bool +is_packet_udp_bootp(void *packet, size_t plen) +{ + struct ip *ip = packet; + size_t ip_hlen; + struct udphdr udp; + + if (plen < sizeof(*ip)) + return false; + + if (ip->ip_v != IPVERSION || ip->ip_p != IPPROTO_UDP) + return false; + + /* Sanity. */ + if (ntohs(ip->ip_len) > plen) + return false; + + ip_hlen = (size_t)ip->ip_hl * 4; + if (ip_hlen < sizeof(*ip)) + return false; + + /* Check we have a UDP header and BOOTP. */ + if (ip_hlen + sizeof(udp) + offsetof(struct bootp, vend) > plen) + return false; + + /* Sanity. */ + memcpy(&udp, (char *)ip + ip_hlen, sizeof(udp)); + if (ntohs(udp.uh_ulen) < sizeof(udp)) + return false; + if (ip_hlen + ntohs(udp.uh_ulen) > plen) + return false; + + /* Check it's to and from the right ports. */ + if (udp.uh_dport != htons(BOOTPC) || udp.uh_sport != htons(BOOTPS)) + return false; + + return true; +} + +/* Lengths have already been checked. */ +static bool +checksums_valid(void *packet, + struct in_addr *from, unsigned int flags) +{ + struct ip *ip = packet; + union pip { + struct ip ip; + uint16_t w[sizeof(struct ip) / 2]; + } pip = { + .ip = { + .ip_p = IPPROTO_UDP, + .ip_src = ip->ip_src, + .ip_dst = ip->ip_dst, + } + }; + size_t ip_hlen; + struct udphdr udp; + char *udpp, *uh_sump; + uint32_t csum; + + if (from != NULL) + from->s_addr = ip->ip_src.s_addr; + + ip_hlen = (size_t)ip->ip_hl * 4; + if (in_cksum(ip, ip_hlen, NULL) != 0) + return false; + + if (flags & BPF_PARTIALCSUM) + return true; + + udpp = (char *)ip + ip_hlen; + memcpy(&udp, udpp, sizeof(udp)); + if (udp.uh_sum == 0) + return true; + + /* UDP checksum is based on a pseudo IP header alongside + * the UDP header and payload. */ + pip.ip.ip_len = udp.uh_ulen; + csum = 0; + + /* Need to zero the UDP sum in the packet for the checksum to work. */ + uh_sump = udpp + offsetof(struct udphdr, uh_sum); + memset(uh_sump, 0, sizeof(udp.uh_sum)); + + /* Checksum pseudo header and then UDP + payload. */ + in_cksum(pip.w, sizeof(pip.w), &csum); + csum = in_cksum(udpp, ntohs(udp.uh_ulen), &csum); + +#if 0 /* Not needed, just here for completeness. */ + /* Put the checksum back. */ + memcpy(uh_sump, &udp.uh_sum, sizeof(udp.uh_sum)); +#endif + + return csum == udp.uh_sum; +} + +static void +dhcp_handlebootp(struct interface *ifp, struct bootp *bootp, size_t len, + struct in_addr *from) +{ + size_t v; + + if (len < offsetof(struct bootp, vend)) { + logerrx("%s: truncated packet (%zu) from %s", + ifp->name, len, inet_ntoa(*from)); + return; + } + + /* Unlikely, but appeases sanitizers. */ + if (len > FRAMELEN_MAX) { + logerrx("%s: packet exceeded frame length (%zu) from %s", + ifp->name, len, inet_ntoa(*from)); + return; + } + + /* To make our IS_DHCP macro easy, ensure the vendor + * area has at least 4 octets. */ + v = len - offsetof(struct bootp, vend); + while (v < 4) { + bootp->vend[v++] = '\0'; + len++; + } + + dhcp_handledhcp(ifp, bootp, len, from); +} + +void +dhcp_packet(struct interface *ifp, uint8_t *data, size_t len, + unsigned int bpf_flags) +{ + struct bootp *bootp; + struct in_addr from; + size_t udp_len; + size_t fl = bpf_frame_header_len(ifp); +#ifdef PRIVSEP + const struct dhcp_state *state = D_CSTATE(ifp); + + /* It's possible that an interface departs and arrives in short + * order to receive a BPF frame out of order. + * There is a similar check in ARP, but much lower down the stack. + * It's not needed for other inet protocols because we send the + * message as a whole and select the interface off that and then + * check state. BPF on the other hand is very interface + * specific and we do need this check. */ + if (state == NULL) + return; + + /* Ignore double reads */ + if (IN_PRIVSEP(ifp->ctx)) { + switch (state->state) { + case DHS_BOUND: /* FALLTHROUGH */ + case DHS_RENEW: + return; + default: + break; + } + } +#endif + + /* Trim frame header */ + if (fl != 0) { + if (len < fl) { + logerrx("%s: %s: short frame header %zu", + __func__, ifp->name, len); + return; + } + len -= fl; + /* Move the data to avoid alignment errors. */ + memmove(data, data + fl, len); + } + + /* Validate filter. */ + if (!is_packet_udp_bootp(data, len)) { +#ifdef BPF_DEBUG + logerrx("%s: DHCP BPF validation failure", ifp->name); +#endif + return; + } + + if (!checksums_valid(data, &from, bpf_flags)) { + logerrx("%s: checksum failure from %s", + ifp->name, inet_ntoa(from)); + return; + } + + /* + * DHCP has a variable option area rather than a fixed vendor area. + * Because DHCP uses the BOOTP protocol it should still send BOOTP + * sized packets to be RFC compliant. + * However some servers send a truncated vendor area. + * dhcpcd can work fine without the vendor area being sent. + */ + bootp = get_udp_data(data, &udp_len); + dhcp_handlebootp(ifp, bootp, udp_len, &from); +} + +static void +dhcp_readbpf(void *arg) +{ + struct interface *ifp = arg; + uint8_t buf[FRAMELEN_MAX]; + ssize_t bytes; + struct dhcp_state *state = D_STATE(ifp); + struct bpf *bpf = state->bpf; + + bpf->bpf_flags &= ~BPF_EOF; + while (!(bpf->bpf_flags & BPF_EOF)) { + bytes = bpf_read(bpf, buf, sizeof(buf)); + if (bytes == -1) { + if (state->state != DHS_NONE) { + logerr("%s: %s", __func__, ifp->name); + dhcp_close(ifp); + } + break; + } + dhcp_packet(ifp, buf, (size_t)bytes, bpf->bpf_flags); + /* Check we still have a state after processing. */ + if ((state = D_STATE(ifp)) == NULL) + break; + if ((bpf = state->bpf) == NULL) + break; + } +} + +void +dhcp_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg) +{ + struct sockaddr_in *from = (struct sockaddr_in *)msg->msg_name; + struct iovec *iov = &msg->msg_iov[0]; + struct interface *ifp; + const struct dhcp_state *state; + + ifp = if_findifpfromcmsg(ctx, msg, NULL); + if (ifp == NULL) { + logerr(__func__); + return; + } + state = D_CSTATE(ifp); + if (state == NULL) { + /* Try re-directing it to another interface. */ + dhcp_redirect_dhcp(ifp, (struct bootp *)iov->iov_base, + iov->iov_len, &from->sin_addr); + return; + } + + if (state->bpf != NULL) { + /* Avoid a duplicate read if BPF is open for the interface. */ + return; + } +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx)) { + switch (state->state) { + case DHS_BOUND: /* FALLTHROUGH */ + case DHS_RENEW: + break; + default: + /* Any other state we ignore it or will receive + * via BPF. */ + return; + } + } +#endif + + dhcp_handlebootp(ifp, iov->iov_base, iov->iov_len, + &from->sin_addr); +} + +static void +dhcp_readudp(struct dhcpcd_ctx *ctx, struct interface *ifp) +{ + const struct dhcp_state *state; + struct sockaddr_in from; + union { + struct bootp bootp; + uint8_t buf[10 * 1024]; /* Maximum MTU */ + } iovbuf; + struct iovec iov = { + .iov_base = iovbuf.buf, + .iov_len = sizeof(iovbuf.buf), + }; + union { + struct cmsghdr hdr; +#ifdef IP_RECVIF + uint8_t buf[CMSG_SPACE(sizeof(struct sockaddr_dl))]; +#else + uint8_t buf[CMSG_SPACE(sizeof(struct in_pktinfo))]; +#endif + } cmsgbuf = { .buf = { 0 } }; + struct msghdr msg = { + .msg_name = &from, .msg_namelen = sizeof(from), + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), + }; + int s; + ssize_t bytes; + + if (ifp != NULL) { + state = D_CSTATE(ifp); + s = state->udp_rfd; + } else + s = ctx->udp_rfd; + + bytes = recvmsg(s, &msg, 0); + if (bytes == -1) { + logerr(__func__); + return; + } + + iov.iov_len = (size_t)bytes; + dhcp_recvmsg(ctx, &msg); +} + +static void +dhcp_handleudp(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + dhcp_readudp(ctx, NULL); +} + +static void +dhcp_handleifudp(void *arg) +{ + struct interface *ifp = arg; + + dhcp_readudp(ifp->ctx, ifp); +} + +static int +dhcp_openbpf(struct interface *ifp) +{ + struct dhcp_state *state; + + state = D_STATE(ifp); + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ifp->ctx)) { + if (ps_bpf_openbootp(ifp) == -1) { + logerr(__func__); + return -1; + } + return 0; + } +#endif + + if (state->bpf != NULL) + return 0; + + state->bpf = bpf_open(ifp, bpf_bootp, NULL); + if (state->bpf == NULL) { + if (errno == ENOENT) { + logerrx("%s not found", bpf_name); + /* May as well disable IPv4 entirely at + * this point as we really need it. */ + ifp->options->options &= ~DHCPCD_IPV4; + } else + logerr("%s: %s", __func__, ifp->name); + return -1; + } + + eloop_event_add(ifp->ctx->eloop, + state->bpf->bpf_fd, dhcp_readbpf, ifp); + return 0; +} + +void +dhcp_free(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + struct dhcpcd_ctx *ctx; + + dhcp_close(ifp); +#ifdef ARP + arp_drop(ifp); +#endif + if (state) { + state->state = DHS_NONE; + free(state->old); + free(state->new); + free(state->offer); + free(state->clientid); + free(state); + } + + ctx = ifp->ctx; + /* If we don't have any more DHCP enabled interfaces, + * close the global socket and release resources */ + if (ctx->ifaces) { + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + state = D_STATE(ifp); + if (state != NULL && state->state != DHS_NONE) + break; + } + } + if (ifp == NULL) { + if (ctx->udp_rfd != -1) { + eloop_event_delete(ctx->eloop, ctx->udp_rfd); + close(ctx->udp_rfd); + ctx->udp_rfd = -1; + } + if (ctx->udp_wfd != -1) { + close(ctx->udp_wfd); + ctx->udp_wfd = -1; + } + + free(ctx->opt_buffer); + ctx->opt_buffer = NULL; + } +} + +static int +dhcp_initstate(struct interface *ifp) +{ + struct dhcp_state *state; + + state = D_STATE(ifp); + if (state != NULL) + return 0; + + ifp->if_data[IF_DATA_DHCP] = calloc(1, sizeof(*state)); + state = D_STATE(ifp); + if (state == NULL) + return -1; + + state->state = DHS_NONE; + /* 0 is a valid fd, so init to -1 */ + state->udp_rfd = -1; +#ifdef ARPING + state->arping_index = -1; +#endif + return 1; +} + +static int +dhcp_init(struct interface *ifp) +{ + struct dhcp_state *state; + struct if_options *ifo; + uint8_t len; + char buf[(sizeof(ifo->clientid) - 1) * 3]; + + if (dhcp_initstate(ifp) == -1) + return -1; + + state = D_STATE(ifp); + state->state = DHS_INIT; + state->reason = "PREINIT"; + state->nakoff = 0; + dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), + AF_INET, ifp); + + ifo = ifp->options; + /* We need to drop the leasefile so that dhcp_start + * doesn't load it. */ + if (ifo->options & DHCPCD_REQUEST) + dhcp_unlink(ifp->ctx, state->leasefile); + + free(state->clientid); + state->clientid = NULL; + + if (ifo->options & DHCPCD_ANONYMOUS) { + /* Removing the option could show that we want anonymous. + * As such keep it as it's already in the hwaddr field. */ + goto make_clientid; + } else if (*ifo->clientid) { + state->clientid = malloc((size_t)(ifo->clientid[0] + 1)); + if (state->clientid == NULL) + goto eexit; + memcpy(state->clientid, ifo->clientid, + (size_t)(ifo->clientid[0]) + 1); + } else if (ifo->options & DHCPCD_CLIENTID) { + if (ifo->options & DHCPCD_DUID) { + state->clientid = malloc(ifp->ctx->duid_len + 6); + if (state->clientid == NULL) + goto eexit; + state->clientid[0] =(uint8_t)(ifp->ctx->duid_len + 5); + state->clientid[1] = 255; /* RFC 4361 */ + memcpy(state->clientid + 2, ifo->iaid, 4); + memcpy(state->clientid + 6, ifp->ctx->duid, + ifp->ctx->duid_len); + } else { +make_clientid: + len = (uint8_t)(ifp->hwlen + 1); + state->clientid = malloc((size_t)len + 1); + if (state->clientid == NULL) + goto eexit; + state->clientid[0] = len; + state->clientid[1] = (uint8_t)ifp->hwtype; + memcpy(state->clientid + 2, ifp->hwaddr, + ifp->hwlen); + } + } + + if (ifo->options & DHCPCD_DUID) + /* Don't bother logging as DUID and IAID are reported + * at device start. */ + return 0; + + if (ifo->options & DHCPCD_CLIENTID && state->clientid != NULL) + logdebugx("%s: using ClientID %s", ifp->name, + hwaddr_ntoa(state->clientid + 1, state->clientid[0], + buf, sizeof(buf))); + else if (ifp->hwlen) + logdebugx("%s: using hwaddr %s", ifp->name, + hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf))); + return 0; + +eexit: + logerr(__func__); + return -1; +} + +static void +dhcp_start1(void *arg) +{ + struct interface *ifp = arg; + struct dhcpcd_ctx *ctx = ifp->ctx; + struct if_options *ifo = ifp->options; + struct dhcp_state *state; + uint32_t l; + int nolease; + + if (!(ifo->options & DHCPCD_IPV4)) + return; + + /* Listen on *.*.*.*:bootpc so that the kernel never sends an + * ICMP port unreachable message back to the DHCP server. + * Only do this in manager mode so we don't swallow messages + * for dhcpcd running on another interface. */ + if ((ctx->options & (DHCPCD_MANAGER|DHCPCD_PRIVSEP)) == DHCPCD_MANAGER + && ctx->udp_rfd == -1) + { + ctx->udp_rfd = dhcp_openudp(NULL); + if (ctx->udp_rfd == -1) { + logerr(__func__); + return; + } + eloop_event_add(ctx->eloop, ctx->udp_rfd, dhcp_handleudp, ctx); + } + if (!IN_PRIVSEP(ctx) && ctx->udp_wfd == -1) { + ctx->udp_wfd = xsocket(PF_INET, SOCK_RAW|SOCK_CXNB,IPPROTO_UDP); + if (ctx->udp_wfd == -1) { + logerr(__func__); + return; + } + } + + if (dhcp_init(ifp) == -1) { + logerr("%s: dhcp_init", ifp->name); + return; + } + + state = D_STATE(ifp); + clock_gettime(CLOCK_MONOTONIC, &state->started); + state->interval = 0; + free(state->offer); + state->offer = NULL; + state->offer_len = 0; + +#ifdef ARPING + if (ifo->arping_len && state->arping_index < ifo->arping_len) { + dhcp_arping(ifp); + return; + } +#endif + + if (ifo->options & DHCPCD_STATIC) { + dhcp_static(ifp); + return; + } + + if (ifo->options & DHCPCD_INFORM) { + dhcp_inform(ifp); + return; + } + + /* We don't want to read the old lease if we NAK an old test */ + nolease = state->offer && ifp->ctx->options & DHCPCD_TEST; + if (!nolease && ifo->options & DHCPCD_DHCP) { + state->offer_len = read_lease(ifp, &state->offer); + /* Check the saved lease matches the type we want */ + if (state->offer) { +#ifdef IN_IFF_DUPLICATED + struct in_addr addr; + struct ipv4_addr *ia; + + addr.s_addr = state->offer->yiaddr; + ia = ipv4_iffindaddr(ifp, &addr, NULL); +#endif + + if ((!IS_DHCP(state->offer) && + !(ifo->options & DHCPCD_BOOTP)) || +#ifdef IN_IFF_DUPLICATED + (ia && ia->addr_flags & IN_IFF_DUPLICATED) || +#endif + (IS_DHCP(state->offer) && + ifo->options & DHCPCD_BOOTP)) + { + free(state->offer); + state->offer = NULL; + state->offer_len = 0; + } + } + } + if (state->offer) { + struct ipv4_addr *ia; + time_t mtime; + + get_lease(ifp, &state->lease, state->offer, state->offer_len); + state->lease.frominfo = 1; + if (state->new == NULL && + (ia = ipv4_iffindaddr(ifp, + &state->lease.addr, &state->lease.mask)) != NULL) + { + /* We still have the IP address from the last lease. + * Fake add the address and routes from it so the lease + * can be cleaned up. */ + state->new = malloc(state->offer_len); + if (state->new) { + memcpy(state->new, + state->offer, state->offer_len); + state->new_len = state->offer_len; + state->addr = ia; + state->added |= STATE_ADDED | STATE_FAKE; + rt_build(ifp->ctx, AF_INET); + } else + logerr(__func__); + } + if (!IS_DHCP(state->offer)) { + free(state->offer); + state->offer = NULL; + state->offer_len = 0; + } else if (!(ifo->options & DHCPCD_LASTLEASE_EXTEND) && + state->lease.leasetime != DHCP_INFINITE_LIFETIME && + dhcp_filemtime(ifp->ctx, state->leasefile, &mtime) == 0) + { + time_t now; + + /* Offset lease times and check expiry */ + now = time(NULL); + if (now == -1 || + (time_t)state->lease.leasetime < now - mtime) + { + logdebugx("%s: discarding expired lease", + ifp->name); + free(state->offer); + state->offer = NULL; + state->offer_len = 0; + state->lease.addr.s_addr = 0; + /* Technically we should discard the lease + * as it's expired, just as DHCPv6 addresses + * would be by the kernel. + * However, this may violate POLA so + * we currently leave it be. + * If we get a totally different lease from + * the DHCP server we'll drop it anyway, as + * we will on any other event which would + * trigger a lease drop. + * This should only happen if dhcpcd stops + * running and the lease expires before + * dhcpcd starts again. */ +#if 0 + if (state->new) + dhcp_drop(ifp, "EXPIRE"); +#endif + } else { + l = (uint32_t)(now - mtime); + state->lease.leasetime -= l; + state->lease.renewaltime -= l; + state->lease.rebindtime -= l; + } + } + } + +#ifdef IPV4LL + if (!(ifo->options & DHCPCD_DHCP)) { + if (ifo->options & DHCPCD_IPV4LL) + ipv4ll_start(ifp); + return; + } +#endif + + if (state->offer == NULL || + !IS_DHCP(state->offer) || + ifo->options & DHCPCD_ANONYMOUS) + dhcp_discover(ifp); + else + dhcp_reboot(ifp); +} + +void +dhcp_start(struct interface *ifp) +{ + unsigned int delay; +#ifdef ARPING + const struct dhcp_state *state; +#endif + + if (!(ifp->options->options & DHCPCD_IPV4)) + return; + + /* If we haven't been given a netmask for our requested address, + * set it now. */ + if (ifp->options->req_addr.s_addr != INADDR_ANY && + ifp->options->req_mask.s_addr == INADDR_ANY) + ifp->options->req_mask.s_addr = + ipv4_getnetmask(ifp->options->req_addr.s_addr); + + /* If we haven't specified a ClientID and our hardware address + * length is greater than BOOTP CHADDR then we enforce a ClientID + * of the hardware address type and the hardware address. + * If there is no hardware address and no ClientID set, + * force a DUID based ClientID. */ + if (ifp->hwlen > 16) + ifp->options->options |= DHCPCD_CLIENTID; + else if (ifp->hwlen == 0 && !(ifp->options->options & DHCPCD_CLIENTID)) + ifp->options->options |= DHCPCD_CLIENTID | DHCPCD_DUID; + + /* Firewire and InfiniBand interfaces require ClientID and + * the broadcast option being set. */ + switch (ifp->hwtype) { + case ARPHRD_IEEE1394: /* FALLTHROUGH */ + case ARPHRD_INFINIBAND: + ifp->options->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST; + break; + } + + /* If we violate RFC2131 section 3.7 then require ARP + * to detect if any other client wants our address. */ + if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) + ifp->options->options |= DHCPCD_ARP; + + /* No point in delaying a static configuration */ + if (ifp->options->options & DHCPCD_STATIC || + !(ifp->options->options & DHCPCD_INITIAL_DELAY)) + { + dhcp_start1(ifp); + return; + } + +#ifdef ARPING + /* If we have arpinged then we have already delayed. */ + state = D_CSTATE(ifp); + if (state != NULL && state->arping_index != -1) { + dhcp_start1(ifp); + return; + } +#endif + delay = MSEC_PER_SEC + + (arc4random_uniform(MSEC_PER_SEC * 2) - MSEC_PER_SEC); + logdebugx("%s: delaying IPv4 for %0.1f seconds", + ifp->name, (float)delay / MSEC_PER_SEC); + + eloop_timeout_add_msec(ifp->ctx->eloop, delay, dhcp_start1, ifp); +} + +void +dhcp_abort(struct interface *ifp) +{ + struct dhcp_state *state; + + state = D_STATE(ifp); +#ifdef ARPING + if (state != NULL) + state->arping_index = -1; +#endif + + eloop_timeout_delete(ifp->ctx->eloop, dhcp_start1, ifp); + + if (state != NULL && state->added) { + rt_build(ifp->ctx, AF_INET); +#ifdef ARP + if (ifp->options->options & DHCPCD_ARP) + arp_announceaddr(ifp->ctx, &state->addr->addr); +#endif + } +} + +struct ipv4_addr * +dhcp_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid) +{ + struct interface *ifp; + struct dhcp_state *state; + struct if_options *ifo; + uint8_t i; + + ifp = ia->iface; + state = D_STATE(ifp); + if (state == NULL || state->state == DHS_NONE) + return ia; + + if (cmd == RTM_DELADDR) { + if (state->addr == ia) { + loginfox("%s: pid %d deleted IP address %s", + ifp->name, pid, ia->saddr); + dhcp_close(ifp); + state->addr = NULL; + /* Don't clear the added state as we need + * to drop the lease. */ + dhcp_drop(ifp, "EXPIRE"); + dhcp_start1(ifp); + return ia; + } + } + + if (cmd != RTM_NEWADDR) + return ia; + +#ifdef IN_IFF_NOTUSEABLE + if (!(ia->addr_flags & IN_IFF_NOTUSEABLE)) + dhcp_finish_dad(ifp, &ia->addr); + else if (ia->addr_flags & IN_IFF_DUPLICATED) + return dhcp_addr_duplicated(ifp, &ia->addr) ? NULL : ia; +#endif + + ifo = ifp->options; + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ifp->ctx) && + !(ifp->ctx->options & (DHCPCD_MANAGER | DHCPCD_CONFIGURE)) && + IN_ARE_ADDR_EQUAL(&state->lease.addr, &ia->addr)) + { + state->addr = ia; + state->added = STATE_ADDED; + dhcp_closebpf(ifp); + if (ps_inet_openbootp(ia) == -1) + logerr(__func__); + } +#endif + + /* If we have requested a specific address, return now. + * The below code is only for when inform or static has been + * requested without a specific address. */ + if (ifo->req_addr.s_addr != INADDR_ANY) + return ia; + + /* Only inform if we are NOT in the inform state or bound. */ + if (ifo->options & DHCPCD_INFORM) { + if (state->state != DHS_INFORM && state->state != DHS_BOUND) + dhcp_inform(ifp); + return ia; + } + + /* Static and inform are mutually exclusive. If not static, return. */ + if (!(ifo->options & DHCPCD_STATIC)) + return ia; + + free(state->old); + state->old = state->new; + state->new_len = dhcp_message_new(&state->new, &ia->addr, &ia->mask); + if (state->new == NULL) + return ia; + + if (ifp->flags & IFF_POINTOPOINT) { + for (i = 1; i < 255; i++) + if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i)) + dhcp_message_add_addr(state->new, i, ia->brd); + } + + state->reason = "STATIC"; + rt_build(ifp->ctx, AF_INET); + script_runreason(ifp, state->reason); + + return ia; +} + +#ifndef SMALL +int +dhcp_dump(struct interface *ifp) +{ + struct dhcp_state *state; + + ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state)); + if (state == NULL) { + logerr(__func__); + return -1; + } + state->new_len = read_lease(ifp, &state->new); + if (state->new == NULL) { + logerr("read_lease"); + return -1; + } + state->reason = "DUMP"; + return script_runreason(ifp, state->reason); +} +#endif diff --git a/src/dhcp.h b/src/dhcp.h new file mode 100644 index 000000000000..9359d564da4d --- /dev/null +++ b/src/dhcp.h @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <netinet/ip.h> +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include <netinet/udp.h> +#undef __FAVOR_BSD + +#include <limits.h> +#include <stdint.h> + +#include "arp.h" +#include "bpf.h" +#include "auth.h" +#include "dhcp-common.h" + +/* UDP port numbers for BOOTP */ +#define BOOTPS 67 +#define BOOTPC 68 + +#define MAGIC_COOKIE 0x63825363 +#define BROADCAST_FLAG 0x8000 + +/* BOOTP message OP code */ +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +/* DHCP message type */ +#define DHCP_DISCOVER 1 +#define DHCP_OFFER 2 +#define DHCP_REQUEST 3 +#define DHCP_DECLINE 4 +#define DHCP_ACK 5 +#define DHCP_NAK 6 +#define DHCP_RELEASE 7 +#define DHCP_INFORM 8 +#define DHCP_FORCERENEW 9 + +/* Constants taken from RFC 2131. */ +#define T1 0.5 +#define T2 0.875 +#define DHCP_BASE 4 +#define DHCP_MAX 64 +#define DHCP_RAND_MIN -1 +#define DHCP_RAND_MAX 1 + +#ifdef RFC2131_STRICT +/* Be strictly conformant for section 4.1.1 */ +# define DHCP_MIN_DELAY 1 +# define DHCP_MAX_DELAY 10 +#else +/* or mirror the more modern IPv6RS and DHCPv6 delays */ +# define DHCP_MIN_DELAY 0 +# define DHCP_MAX_DELAY 1 +#endif + +/* DHCP options */ +enum DHO { + DHO_PAD = 0, + DHO_SUBNETMASK = 1, + DHO_ROUTER = 3, + DHO_DNSSERVER = 6, + DHO_HOSTNAME = 12, + DHO_DNSDOMAIN = 15, + DHO_MTU = 26, + DHO_BROADCAST = 28, + DHO_STATICROUTE = 33, + DHO_NISDOMAIN = 40, + DHO_NISSERVER = 41, + DHO_NTPSERVER = 42, + DHO_VENDOR = 43, + DHO_IPADDRESS = 50, + DHO_LEASETIME = 51, + DHO_OPTSOVERLOADED = 52, + DHO_MESSAGETYPE = 53, + DHO_SERVERID = 54, + DHO_PARAMETERREQUESTLIST = 55, + DHO_MESSAGE = 56, + DHO_MAXMESSAGESIZE = 57, + DHO_RENEWALTIME = 58, + DHO_REBINDTIME = 59, + DHO_VENDORCLASSID = 60, + DHO_CLIENTID = 61, + DHO_USERCLASS = 77, /* RFC 3004 */ + DHO_RAPIDCOMMIT = 80, /* RFC 4039 */ + DHO_FQDN = 81, + DHO_AUTHENTICATION = 90, /* RFC 3118 */ + DHO_IPV6_PREFERRED_ONLY = 108, /* RFC 8925 */ + DHO_AUTOCONFIGURE = 116, /* RFC 2563 */ + DHO_DNSSEARCH = 119, /* RFC 3397 */ + DHO_CSR = 121, /* RFC 3442 */ + DHO_VIVCO = 124, /* RFC 3925 */ + DHO_VIVSO = 125, /* RFC 3925 */ + DHO_FORCERENEW_NONCE = 145, /* RFC 6704 */ + DHO_MUDURL = 161, /* draft-ietf-opsawg-mud */ + DHO_SIXRD = 212, /* RFC 5969 */ + DHO_MSCSR = 249, /* MS code for RFC 3442 */ + DHO_END = 255 +}; + +/* FQDN values - lsnybble used in flags + * hsnybble to create order + * and to allow 0x00 to mean disable + */ +enum FQDN { + FQDN_DISABLE = 0x00, + FQDN_NONE = 0x18, + FQDN_PTR = 0x20, + FQDN_BOTH = 0x31 +}; + +#define MIN_V6ONLY_WAIT 300 /* seconds, RFC 8925 */ + +/* Sizes for BOOTP options */ +#define BOOTP_CHADDR_LEN 16 +#define BOOTP_SNAME_LEN 64 +#define BOOTP_FILE_LEN 128 +#define BOOTP_VEND_LEN 64 + +/* DHCP is basically an extension to BOOTP */ +struct bootp { + uint8_t op; /* message type */ + uint8_t htype; /* hardware address type */ + uint8_t hlen; /* hardware address length */ + uint8_t hops; /* should be zero in client message */ + uint32_t xid; /* transaction id */ + uint16_t secs; /* elapsed time in sec. from boot */ + uint16_t flags; /* such as broadcast flag */ + uint32_t ciaddr; /* (previously allocated) client IP */ + uint32_t yiaddr; /* 'your' client IP address */ + uint32_t siaddr; /* should be zero in client's messages */ + uint32_t giaddr; /* should be zero in client's messages */ + uint8_t chaddr[BOOTP_CHADDR_LEN]; /* client's hardware address */ + uint8_t sname[BOOTP_SNAME_LEN]; /* server host name */ + uint8_t file[BOOTP_FILE_LEN]; /* boot file name */ + uint8_t vend[BOOTP_VEND_LEN]; /* vendor specific area */ + /* DHCP allows a variable length vendor area */ +}; + +#define DHCP_MIN_LEN (offsetof(struct bootp, vend) + 4) + +struct bootp_pkt +{ + struct ip ip; + struct udphdr udp; + struct bootp bootp; +}; + +struct dhcp_lease { + struct in_addr addr; + struct in_addr mask; + struct in_addr brd; + uint32_t leasetime; + uint32_t renewaltime; + uint32_t rebindtime; + struct in_addr server; + uint8_t frominfo; + uint32_t cookie; +}; + +#ifndef DHCP_INFINITE_LIFETIME +# define DHCP_INFINITE_LIFETIME (~0U) +#endif + +enum DHS { + DHS_NONE, + DHS_INIT, + DHS_DISCOVER, + DHS_REQUEST, + DHS_PROBE, + DHS_BOUND, + DHS_RENEW, + DHS_REBIND, + DHS_REBOOT, + DHS_INFORM, + DHS_RENEW_REQUESTED, + DHS_RELEASE +}; + +struct dhcp_state { + enum DHS state; + struct bootp *sent; + size_t sent_len; + struct bootp *offer; + size_t offer_len; + struct bootp *new; + size_t new_len; + struct bootp *old; + size_t old_len; + struct dhcp_lease lease; + const char *reason; + unsigned int interval; + unsigned int nakoff; + uint32_t xid; + int socket; + + struct bpf *bpf; + int udp_rfd; + struct ipv4_addr *addr; + uint8_t added; + + char leasefile[sizeof(LEASEFILE) + IF_NAMESIZE + (IF_SSIDLEN * 4)]; + struct timespec started; + unsigned char *clientid; + struct authstate auth; +#ifdef ARPING + ssize_t arping_index; +#endif +}; + +#ifdef INET +#define D_STATE(ifp) \ + ((struct dhcp_state *)(ifp)->if_data[IF_DATA_DHCP]) +#define D_CSTATE(ifp) \ + ((const struct dhcp_state *)(ifp)->if_data[IF_DATA_DHCP]) +#define D_STATE_RUNNING(ifp) \ + (D_CSTATE((ifp)) && D_CSTATE((ifp))->new && D_CSTATE((ifp))->reason) + +#define IS_DHCP(b) ((b)->vend[0] == 0x63 && \ + (b)->vend[1] == 0x82 && \ + (b)->vend[2] == 0x53 && \ + (b)->vend[3] == 0x63) + +#include "dhcpcd.h" +#include "if-options.h" + +ssize_t print_rfc3361(FILE *, const uint8_t *, size_t); +ssize_t print_rfc3442(FILE *, const uint8_t *, size_t); + +int dhcp_openudp(struct in_addr *); +void dhcp_packet(struct interface *, uint8_t *, size_t, unsigned int); +void dhcp_recvmsg(struct dhcpcd_ctx *, struct msghdr *); +void dhcp_printoptions(const struct dhcpcd_ctx *, + const struct dhcp_opt *, size_t); +uint16_t dhcp_get_mtu(const struct interface *); +int dhcp_get_routes(rb_tree_t *, struct interface *); +ssize_t dhcp_env(FILE *, const char *, const struct interface *, + const struct bootp *, size_t); + +struct ipv4_addr *dhcp_handleifa(int, struct ipv4_addr *, pid_t pid); +void dhcp_drop(struct interface *, const char *); +void dhcp_start(struct interface *); +void dhcp_abort(struct interface *); +void dhcp_discover(void *); +void dhcp_inform(struct interface *); +void dhcp_renew(struct interface *); +void dhcp_bind(struct interface *); +void dhcp_reboot_newopts(struct interface *, unsigned long long); +void dhcp_close(struct interface *); +void dhcp_free(struct interface *); +int dhcp_dump(struct interface *); +#endif /* INET */ + +#endif /* DHCP_H */ diff --git a/src/dhcp6.c b/src/dhcp6.c new file mode 100644 index 000000000000..21198c42c059 --- /dev/null +++ b/src/dhcp6.c @@ -0,0 +1,4364 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2021 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. + */ + +#include <sys/utsname.h> +#include <sys/types.h> + +#include <netinet/in.h> +#include <netinet/ip6.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <syslog.h> + +#define ELOOP_QUEUE ELOOP_DHCP6 +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "duid.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "script.h" + +#ifdef HAVE_SYS_BITOPS_H +#include <sys/bitops.h> +#else +#include "compat/bitops.h" +#endif + +/* DHCPCD Project has been assigned an IANA PEN of 40712 */ +#define DHCPCD_IANA_PEN 40712 + +/* Unsure if I want this */ +//#define VENDOR_SPLIT + +/* Support older systems with different defines */ +#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) +#define IPV6_RECVPKTINFO IPV6_PKTINFO +#endif + +#ifdef DHCP6 + +/* Assert the correct structure size for on wire */ +struct dhcp6_message { + uint8_t type; + uint8_t xid[3]; + /* followed by options */ +}; +__CTASSERT(sizeof(struct dhcp6_message) == 4); + +struct dhcp6_option { + uint16_t code; + uint16_t len; + /* followed by data */ +}; +__CTASSERT(sizeof(struct dhcp6_option) == 4); + +struct dhcp6_ia_na { + uint8_t iaid[4]; + uint32_t t1; + uint32_t t2; +}; +__CTASSERT(sizeof(struct dhcp6_ia_na) == 12); + +struct dhcp6_ia_ta { + uint8_t iaid[4]; +}; +__CTASSERT(sizeof(struct dhcp6_ia_ta) == 4); + +struct dhcp6_ia_addr { + struct in6_addr addr; + uint32_t pltime; + uint32_t vltime; +}; +__CTASSERT(sizeof(struct dhcp6_ia_addr) == 16 + 8); + +/* XXX FIXME: This is the only packed structure and it does not align. + * Maybe manually decode it? */ +struct dhcp6_pd_addr { + uint32_t pltime; + uint32_t vltime; + uint8_t prefix_len; + struct in6_addr prefix; +} __packed; +__CTASSERT(sizeof(struct dhcp6_pd_addr) == 8 + 1 + 16); + +struct dhcp6_op { + uint16_t type; + const char *name; +}; + +static const struct dhcp6_op dhcp6_ops[] = { + { DHCP6_SOLICIT, "SOLICIT6" }, + { DHCP6_ADVERTISE, "ADVERTISE6" }, + { DHCP6_REQUEST, "REQUEST6" }, + { DHCP6_REPLY, "REPLY6" }, + { DHCP6_RENEW, "RENEW6" }, + { DHCP6_REBIND, "REBIND6" }, + { DHCP6_CONFIRM, "CONFIRM6" }, + { DHCP6_INFORMATION_REQ, "INFORM6" }, + { DHCP6_RELEASE, "RELEASE6" }, + { DHCP6_RECONFIGURE, "RECONFIGURE6" }, + { DHCP6_DECLINE, "DECLINE6" }, + { 0, NULL } +}; + +struct dhcp_compat { + uint8_t dhcp_opt; + uint16_t dhcp6_opt; +}; + +static const struct dhcp_compat dhcp_compats[] = { + { DHO_DNSSERVER, D6_OPTION_DNS_SERVERS }, + { DHO_HOSTNAME, D6_OPTION_FQDN }, + { DHO_DNSDOMAIN, D6_OPTION_FQDN }, + { DHO_NISSERVER, D6_OPTION_NIS_SERVERS }, + { DHO_NTPSERVER, D6_OPTION_SNTP_SERVERS }, + { DHO_RAPIDCOMMIT, D6_OPTION_RAPID_COMMIT }, + { DHO_FQDN, D6_OPTION_FQDN }, + { DHO_VIVCO, D6_OPTION_VENDOR_CLASS }, + { DHO_VIVSO, D6_OPTION_VENDOR_OPTS }, + { DHO_DNSSEARCH, D6_OPTION_DOMAIN_LIST }, + { 0, 0 } +}; + +static const char * const dhcp6_statuses[] = { + "Success", + "Unspecified Failure", + "No Addresses Available", + "No Binding", + "Not On Link", + "Use Multicast", + "No Prefix Available" +}; + +static void dhcp6_bind(struct interface *, const char *, const char *); +static void dhcp6_failinform(void *); +static void dhcp6_recvaddr(void *); +static void dhcp6_startdecline(struct interface *); + +#ifdef SMALL +#define dhcp6_hasprefixdelegation(a) (0) +#else +static int dhcp6_hasprefixdelegation(struct interface *); +#endif + +#define DECLINE_IA(ia) \ + ((ia)->addr_flags & IN6_IFF_DUPLICATED && \ + (ia)->ia_type != 0 && (ia)->ia_type != D6_OPTION_IA_PD && \ + !((ia)->flags & IPV6_AF_STALE) && \ + (ia)->prefix_vltime != 0) + +void +dhcp6_printoptions(const struct dhcpcd_ctx *ctx, + const struct dhcp_opt *opts, size_t opts_len) +{ + size_t i, j; + const struct dhcp_opt *opt, *opt2; + int cols; + + for (i = 0, opt = ctx->dhcp6_opts; + i < ctx->dhcp6_opts_len; i++, opt++) + { + for (j = 0, opt2 = opts; j < opts_len; j++, opt2++) + if (opt2->option == opt->option) + break; + if (j == opts_len) { + cols = printf("%05d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } + } + for (i = 0, opt = opts; i < opts_len; i++, opt++) { + cols = printf("%05d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } +} + +static size_t +dhcp6_makeuser(void *data, const struct interface *ifp) +{ + const struct if_options *ifo = ifp->options; + struct dhcp6_option o; + uint8_t *p; + const uint8_t *up, *ue; + uint16_t ulen, unlen; + size_t olen; + + /* Convert the DHCPv4 user class option to DHCPv6 */ + up = ifo->userclass; + ulen = *up++; + if (ulen == 0) + return 0; + + p = data; + olen = 0; + if (p != NULL) + p += sizeof(o); + + ue = up + ulen; + for (; up < ue; up += ulen) { + ulen = *up++; + olen += sizeof(ulen) + ulen; + if (data == NULL) + continue; + unlen = htons(ulen); + memcpy(p, &unlen, sizeof(unlen)); + p += sizeof(unlen); + memcpy(p, up, ulen); + p += ulen; + } + if (data != NULL) { + o.code = htons(D6_OPTION_USER_CLASS); + o.len = htons((uint16_t)olen); + memcpy(data, &o, sizeof(o)); + } + + return sizeof(o) + olen; +} + +static size_t +dhcp6_makevendor(void *data, const struct interface *ifp) +{ + const struct if_options *ifo; + size_t len, vlen, i; + uint8_t *p; + const struct vivco *vivco; + struct dhcp6_option o; + + ifo = ifp->options; + len = sizeof(uint32_t); /* IANA PEN */ + if (ifo->vivco_en) { + vlen = 0; + for (i = 0, vivco = ifo->vivco; + i < ifo->vivco_len; + i++, vivco++) + vlen += sizeof(uint16_t) + vivco->len; + len += vlen; + } else if (ifo->vendorclassid[0] != '\0') { + /* dhcpcd owns DHCPCD_IANA_PEN. + * If you need your own string, get your own IANA PEN. */ + vlen = strlen(ifp->ctx->vendor); + len += sizeof(uint16_t) + vlen; + } else + return 0; + + if (len > UINT16_MAX) { + logerrx("%s: DHCPv6 Vendor Class too big", ifp->name); + return 0; + } + + if (data != NULL) { + uint32_t pen; + uint16_t hvlen; + + p = data; + o.code = htons(D6_OPTION_VENDOR_CLASS); + o.len = htons((uint16_t)len); + memcpy(p, &o, sizeof(o)); + p += sizeof(o); + pen = htonl(ifo->vivco_en ? ifo->vivco_en : DHCPCD_IANA_PEN); + memcpy(p, &pen, sizeof(pen)); + p += sizeof(pen); + + if (ifo->vivco_en) { + for (i = 0, vivco = ifo->vivco; + i < ifo->vivco_len; + i++, vivco++) + { + hvlen = htons((uint16_t)vivco->len); + memcpy(p, &hvlen, sizeof(hvlen)); + p += sizeof(hvlen); + memcpy(p, vivco->data, vivco->len); + p += vivco->len; + } + } else if (ifo->vendorclassid[0] != '\0') { + hvlen = htons((uint16_t)vlen); + memcpy(p, &hvlen, sizeof(hvlen)); + p += sizeof(hvlen); + memcpy(p, ifp->ctx->vendor, vlen); + } + } + + return sizeof(o) + len; +} + +static void * +dhcp6_findoption(void *data, size_t data_len, uint16_t code, uint16_t *len) +{ + uint8_t *d; + struct dhcp6_option o; + + code = htons(code); + for (d = data; data_len != 0; d += o.len, data_len -= o.len) { + if (data_len < sizeof(o)) { + errno = EINVAL; + return NULL; + } + memcpy(&o, d, sizeof(o)); + d += sizeof(o); + data_len -= sizeof(o); + o.len = htons(o.len); + if (data_len < o.len) { + errno = EINVAL; + return NULL; + } + if (o.code == code) { + if (len != NULL) + *len = o.len; + return d; + } + } + + errno = ENOENT; + return NULL; +} + +static void * +dhcp6_findmoption(void *data, size_t data_len, uint16_t code, + uint16_t *len) +{ + uint8_t *d; + + if (data_len < sizeof(struct dhcp6_message)) { + errno = EINVAL; + return false; + } + d = data; + d += sizeof(struct dhcp6_message); + data_len -= sizeof(struct dhcp6_message); + return dhcp6_findoption(d, data_len, code, len); +} + +static const uint8_t * +dhcp6_getoption(struct dhcpcd_ctx *ctx, + size_t *os, unsigned int *code, size_t *len, + const uint8_t *od, size_t ol, struct dhcp_opt **oopt) +{ + struct dhcp6_option o; + size_t i; + struct dhcp_opt *opt; + + if (od != NULL) { + *os = sizeof(o); + if (ol < *os) { + errno = EINVAL; + return NULL; + } + memcpy(&o, od, sizeof(o)); + *len = ntohs(o.len); + if (*len > ol - *os) { + errno = ERANGE; + return NULL; + } + *code = ntohs(o.code); + } + + *oopt = NULL; + for (i = 0, opt = ctx->dhcp6_opts; + i < ctx->dhcp6_opts_len; i++, opt++) + { + if (opt->option == *code) { + *oopt = opt; + break; + } + } + + if (od != NULL) + return od + sizeof(o); + return NULL; +} + +static bool +dhcp6_updateelapsed(struct interface *ifp, struct dhcp6_message *m, size_t len) +{ + uint8_t *opt; + uint16_t opt_len; + struct dhcp6_state *state; + struct timespec tv; + unsigned long long hsec; + uint16_t sec; + + opt = dhcp6_findmoption(m, len, D6_OPTION_ELAPSED, &opt_len); + if (opt == NULL) + return false; + if (opt_len != sizeof(sec)) { + errno = EINVAL; + return false; + } + + state = D6_STATE(ifp); + clock_gettime(CLOCK_MONOTONIC, &tv); + if (state->RTC == 0) { + /* An RTC of zero means we're the first message + * out of the door, so the elapsed time is zero. */ + state->started = tv; + hsec = 0; + } else { + unsigned long long secs; + unsigned int nsecs; + + secs = eloop_timespec_diff(&tv, &state->started, &nsecs); + /* Elapsed time is measured in centiseconds. + * We need to be sure it will not potentially overflow. */ + if (secs >= (UINT16_MAX / CSEC_PER_SEC) + 1) + hsec = UINT16_MAX; + else { + hsec = (secs * CSEC_PER_SEC) + + (nsecs / NSEC_PER_CSEC); + if (hsec > UINT16_MAX) + hsec = UINT16_MAX; + } + } + sec = htons((uint16_t)hsec); + memcpy(opt, &sec, sizeof(sec)); + return true; +} + +static void +dhcp6_newxid(const struct interface *ifp, struct dhcp6_message *m) +{ + const struct interface *ifp1; + const struct dhcp6_state *state1; + uint32_t xid; + + if (ifp->options->options & DHCPCD_XID_HWADDR && + ifp->hwlen >= sizeof(xid)) + /* The lower bits are probably more unique on the network */ + memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid), + sizeof(xid)); + else { +again: + xid = arc4random(); + } + + m->xid[0] = (xid >> 16) & 0xff; + m->xid[1] = (xid >> 8) & 0xff; + m->xid[2] = xid & 0xff; + + /* Ensure it's unique */ + TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) { + if (ifp == ifp1) + continue; + if ((state1 = D6_CSTATE(ifp1)) == NULL) + continue; + if (state1->send != NULL && + state1->send->xid[0] == m->xid[0] && + state1->send->xid[1] == m->xid[1] && + state1->send->xid[2] == m->xid[2]) + break; + } + + if (ifp1 != NULL) { + if (ifp->options->options & DHCPCD_XID_HWADDR && + ifp->hwlen >= sizeof(xid)) + { + logerrx("%s: duplicate xid on %s", + ifp->name, ifp1->name); + return; + } + goto again; + } +} + +#ifndef SMALL +static const struct if_sla * +dhcp6_findselfsla(struct interface *ifp) +{ + size_t i, j; + struct if_ia *ia; + + for (i = 0; i < ifp->options->ia_len; i++) { + ia = &ifp->options->ia[i]; + if (ia->ia_type != D6_OPTION_IA_PD) + continue; + for (j = 0; j < ia->sla_len; j++) { + if (strcmp(ia->sla[j].ifname, ifp->name) == 0) + return &ia->sla[j]; + } + } + return NULL; +} + +static int +dhcp6_delegateaddr(struct in6_addr *addr, struct interface *ifp, + const struct ipv6_addr *prefix, const struct if_sla *sla, struct if_ia *ia) +{ + struct dhcp6_state *state; + struct if_sla asla; + char sabuf[INET6_ADDRSTRLEN]; + const char *sa; + + state = D6_STATE(ifp); + if (state == NULL) { + ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state)); + state = D6_STATE(ifp); + if (state == NULL) { + logerr(__func__); + return -1; + } + + TAILQ_INIT(&state->addrs); + state->state = DH6S_DELEGATED; + state->reason = "DELEGATED6"; + } + + if (sla == NULL || !sla->sla_set) { + /* No SLA set, so make an assumption of + * desired SLA and prefix length. */ + asla.sla = ifp->index; + asla.prefix_len = 0; + asla.sla_set = false; + sla = &asla; + } else if (sla->prefix_len == 0) { + /* An SLA was given, but prefix length was not. + * We need to work out a suitable prefix length for + * potentially more than one interface. */ + asla.sla = sla->sla; + asla.prefix_len = 0; + asla.sla_set = sla->sla_set; + sla = &asla; + } + + if (sla->prefix_len == 0) { + uint32_t sla_max; + int bits; + + sla_max = ia->sla_max; + if (sla_max == 0 && (sla == NULL || !sla->sla_set)) { + const struct interface *ifi; + + TAILQ_FOREACH(ifi, ifp->ctx->ifaces, next) { + if (ifi->index > sla_max) + sla_max = ifi->index; + } + } + + bits = fls32(sla_max); + + if (prefix->prefix_len + bits > (int)UIN |