diff options
| author | Siva Mahadevan <siva@FreeBSD.org> | 2026-04-05 18:40:56 +0000 |
|---|---|---|
| committer | Siva Mahadevan <siva@FreeBSD.org> | 2026-04-05 18:40:56 +0000 |
| commit | 43ade7d9e9170654b2866b20cb35d5743beda925 (patch) | |
| tree | 7985d40b4c2a2c635302f9256de894e9f9e33d40 | |
| parent | a1f03c7e4d33421baac0a5f3dc5947c7b379ea0c (diff) | |
| -rw-r--r-- | LICENSE | 2 | ||||
| -rw-r--r-- | Makefile | 54 | ||||
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | avahi-daemon.in | 32 | ||||
| -rwxr-xr-x[-rw-r--r--] | configure | 4 | ||||
| -rw-r--r-- | dnsmasq.in | 8 | ||||
| -rw-r--r-- | libc.in | 82 | ||||
| -rw-r--r-- | mdnsd.in | 32 | ||||
| -rw-r--r-- | named.in | 4 | ||||
| -rw-r--r-- | pdns_recursor.in | 4 | ||||
| -rw-r--r-- | pdnsd.in | 10 | ||||
| -rw-r--r-- | resolvconf.8.in | 226 | ||||
| -rw-r--r-- | resolvconf.conf.5.in | 182 | ||||
| -rw-r--r-- | resolvconf.in | 947 | ||||
| -rw-r--r-- | resolvectl.in | 159 | ||||
| -rw-r--r-- | systemd-resolved.in | 96 | ||||
| -rw-r--r-- | unbound.in | 30 |
17 files changed, 1484 insertions, 397 deletions
@@ -1,4 +1,4 @@ -Copyright (c) 2007-2019 Roy Marples <roy@marples.name> +Copyright (c) 2007-2020 Roy Marples <roy@marples.name> All rights reserved. Redistribution and use in source and binary forms, with or without @@ -5,6 +5,9 @@ _CONFIG_MK!= test -e config.mk && echo config.mk || echo config-null.mk CONFIG_MK?= ${_CONFIG_MK} include ${CONFIG_MK} +DIST!= if test -d .git; then echo "dist-git"; \ + else echo "dist-inst"; fi + SBINDIR?= /sbin SYSCONFDIR?= /etc LIBEXECDIR?= /libexec/resolvconf @@ -20,9 +23,11 @@ BINMODE?= 0755 DOCMODE?= 0644 MANMODE?= 0444 -RESOLVCONF= resolvconf resolvconf.8 resolvconf.conf.5 -SUBSCRIBERS= libc dnsmasq named pdnsd pdns_recursor unbound -TARGET= ${RESOLVCONF} ${SUBSCRIBERS} +RESOLVCONF= resolvconf resolvconf.8 resolvconf.conf.5 +SUBSCRIBERS= libc dnsmasq named pdnsd pdns_recursor unbound +SUBSCRIBERS+= systemd-resolved resolvectl +LIBC_SUBSCRIBERS= avahi-daemon mdnsd +TARGET= ${RESOLVCONF} ${SUBSCRIBERS} ${LIBC_SUBSCRIBERS} SRCS= ${TARGET:C,$,.in,} # pmake SRCS:= ${TARGET:=.in} # gmake @@ -36,12 +41,12 @@ SED_RCDIR= -e 's:@RCDIR@:${RCDIR}:g' SED_STATUSARG= -e 's:@STATUSARG@:${STATUSARG}:g' DISTPREFIX?= ${PKG}-${VERSION} -DISTFILEGZ?= ${DISTPREFIX}.tar.gz DISTFILE?= ${DISTPREFIX}.tar.xz DISTINFO= ${DISTFILE}.distinfo -DISTINFOSIGN= ${DISTINFO}.asc -CKSUM?= cksum -a SHA256 -PGP?= netpgp +DISTINFOMD= ${DISTINFO}.md +DISTSIGN= ${DISTFILE}.asc +SHA256?= sha256 +PGP?= gpg2 GITREF?= HEAD @@ -59,7 +64,7 @@ clean: rm -f ${TARGET} distclean: clean - rm -f config.mk ${DISTFILE} ${DISTINFO} ${DISTINFOSIGN} + rm -f config.mk ${DISTFILE} ${DISTINFO} ${DISTINFOMD} ${DISTSIGN} installdirs: @@ -71,6 +76,9 @@ proginstall: ${TARGET} ${INSTALL} -m ${DOCMODE} resolvconf.conf ${DESTDIR}${SYSCONFDIR} ${INSTALL} -d ${DESTDIR}${LIBEXECDIR} ${INSTALL} -m ${DOCMODE} ${SUBSCRIBERS} ${DESTDIR}${LIBEXECDIR} + ${INSTALL} -d ${DESTDIR}${LIBEXECDIR}/libc.d + ${INSTALL} -m ${DOCMODE} ${LIBC_SUBSCRIBERS} \ + ${DESTDIR}${LIBEXECDIR}/libc.d maninstall: ${INSTALL} -d ${DESTDIR}${MANDIR}/man8 @@ -87,18 +95,30 @@ dist-inst: mkdir /tmp/${DISTPREFIX} cp -RPp * /tmp/${DISTPREFIX} (cd /tmp/${DISTPREFIX}; make clean) - tar -cvjpf ${DISTFILE} -C /tmp ${DISTPREFIX} + tar -cvJpf ${DISTFILE} -C /tmp ${DISTPREFIX} rm -rf /tmp/${DISTPREFIX} -dist: dist-git +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} + rm -f ${DISTINFO} ${DISTSIGN} + ${SHA256} ${DISTFILE} >${DISTINFO} + wc -c <${DISTFILE} \ + | xargs printf 'Size (${DISTFILE}) = %s\n' >>${DISTINFO} + ${PGP} --sign --armour --detach ${DISTFILE} + chmod 644 ${DISTSIGN} + ls -l ${DISTFILE} ${DISTINFO} ${DISTSIGN} + +${DISTINFOMD}: ${DISTINFO} + echo '```' >${DISTINFOMD} + cat ${DISTINFO} >>${DISTINFOMD} + echo '```' >>${DISTINFOMD} + +release: distinfo ${DISTINFOMD} + gh release create v${VERSION} \ + --title "openresolv ${VERSION}" --draft --generate-notes \ + --notes-file ${DISTINFOMD} \ + ${DISTFILE} ${DISTSIGN} import: dist rm -rf /tmp/${DISTPREFIX} @@ -115,4 +135,4 @@ _import-src: @${ECHO} "openresolv-${VERSION} imported to ${DESTDIR}" import-src: - ${MAKE} _import-src DESTDIR=`if [ -n "${DESTDIR}" ]; then echo "${DESTDIR}"; else echo /tmp/${DISTPREFIX}; fi` + ${MAKE} _import-src DESTDIR=`if [ -n "${DESTDIR}" ]; then echo "${DESTDIR}"; else echo /tmp/${DISTPREFIX}; fi` diff --git a/README.md b/README.md index 739cc73356cb..50e54ced93ef 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Here's some reasons: The last point is quite important, especially when running VPN systems. Take the following resolv.conf files which have been generated by a -[DHCP client](../dhcpcd) and sent to resolvconf: +[DHCP client](https://github.com/NetworkConfiguration/dhcpcd) and sent to resolvconf: ``` # resolv.conf from bge0 @@ -58,7 +58,12 @@ openresolv ships with helpers for: * [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) * [ISC BIND](http://www.isc.org/software/bind) * [PowerDNS Recursor](http://wiki.powerdns.com/trac) + * [systemd-resolved](https://www.freedesktop.org/software/systemd/man/latest/systemd-resolved.service.html) See the -[configuration section](https://roy.marples.name/projects/openresolv/config) +[configuration section](https://roy.marples.name/projects/openresolv/configuration) for more details. + +If openresolv updates `/etc/resolv.conf` it can notify the following of this: + * [Bonjour (mdnsd)](https://developer.apple.com/bonjour/) + * [avahi](http://www.avahi.org/) diff --git a/avahi-daemon.in b/avahi-daemon.in new file mode 100644 index 000000000000..cdac44fe8234 --- /dev/null +++ b/avahi-daemon.in @@ -0,0 +1,32 @@ +#!/bin/sh +# Copyright (c) 2007-2023 Roy Marples +# All rights reserved + +# avahi-daemon notifier for resolvconf libc subscriber + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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 +# OWNER 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. + +: ${avahi_daemon_pidfile:=/var/run/avahi-daemon/pid} +if [ -s "$avahi_daemon_pidfile" ]; then + kill -HUP $(cat "$avahi_daemon_pidfile") +fi diff --git a/configure b/configure index c9422b74b69c..50fe74da8baa 100644..100755 --- a/configure +++ b/configure @@ -39,7 +39,7 @@ for x do --includedir) eval INCLUDEDIR="$INCLUDEDIR${INCLUDEDIR:+ }$var";; --datadir|--infodir) ;; # ignore autotools --disable-maintainer-mode|--disable-dependency-tracking) ;; - --help) echo "See the README file for available options"; exit 0;; + --help) echo "See the source for available options"; exit 0;; *) echo "$0: WARNING: unknown option $opt" >&2;; esac done @@ -92,7 +92,7 @@ dragonfly*) : ${LIBEXECDIR:=${PREFIX:-/usr}/libexec/resolvconf} ;; linux*) - # cksum does't support -a and netpgp is rare + # cksum doesn't support -a and netpgp is rare echo "CKSUM= sha256sum --tag" >>$CONFIG_MK echo "PGP= gpg2" >>$CONFIG_MK ;; diff --git a/dnsmasq.in b/dnsmasq.in index 8b51866dca25..ae33f094b649 100644 --- a/dnsmasq.in +++ b/dnsmasq.in @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2007-2019 Roy Marples +# Copyright (c) 2007-2023 Roy Marples # All rights reserved # dnsmasq subscriber for resolvconf @@ -105,7 +105,7 @@ for d in $DOMAINS; do empty=true continue fi - i=$(($i + 1)) + i=$((i + 1)) while [ ${#addr} -lt 4 ]; do addr="0${addr}" done @@ -118,7 +118,7 @@ for d in $DOMAINS; do fi done while [ $i != 8 ]; do - i=$(($i + 1)) + i=$((i + 1)) front="$front byte:0 byte:0" done front="${front}$back" @@ -151,7 +151,7 @@ else fi # Try to ensure that config dirs exist -if type config_mkdirs >/dev/null 2>&1; then +if command -v config_mkdirs >/dev/null 2>&1; then config_mkdirs "$dnsmasq_conf" "$dnsmasq_resolv" else @SBINDIR@/resolvconf -D "$dnsmasq_conf" "$dnsmasq_resolv" @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2007-2019 Roy Marples +# Copyright (c) 2007-2025 Roy Marples # All rights reserved # libc subscriber for resolvconf @@ -29,10 +29,23 @@ SYSCONFDIR=@SYSCONFDIR@ LIBEXECDIR=@LIBEXECDIR@ VARDIR=@VARDIR@ -IFACEDIR="$VARDIR/interfaces" +KEYDIR="$VARDIR/keys" +# Compat +if [ ! -d "$KEYDIR" ] && [ -d "$VARDIR/interfaces" ]; then + KEYDIR="$VARDIR/interfaces" +fi + +CMD="$1" +KEY="$2" + NL=" " +warn() +{ + echo "${0##*/}: $*" >&2 +} + # sed may not be available, and this is faster on small files key_get_value() { @@ -94,8 +107,12 @@ elif [ -d "$SYSCONFDIR"/resolvconf ]; then fi fi : ${resolv_conf:=/etc/resolv.conf} +if [ "$resolv_conf" = "/dev/null" ]; then + exit 0 +fi +: ${resolv_conf_tmp:="$resolv_conf.$$.openresolv"} : ${libc_service:=nscd} -: ${list_resolv:=@SBINDIR@/resolvconf -l} +: ${list_resolv:=@SBINDIR@/resolvconf -L} if [ "${resolv_conf_head-x}" = x ] && [ -f "$SYSCONFDIR"/resolv.conf.head ] then resolv_conf_head="$(cat "${SYSCONFDIR}"/resolv.conf.head)" @@ -105,9 +122,8 @@ then resolv_conf_tail="$(cat "$SYSCONFDIR"/resolv.conf.tail)" fi -backup=true signature="# Generated by resolvconf" - + uniqify() { result= @@ -123,15 +139,14 @@ uniqify() case "${resolv_conf_passthrough:-NO}" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) - backup=false newest= - for conf in "$IFACEDIR"/*; do + for conf in "$KEYDIR"/*; do if [ -z "$newest" ] || [ "$conf" -nt "$newest" ]; then newest="$conf" fi done [ -z "$newest" ] && exit 0 - newconf="$(cat "$newest")$NL" + newconf="$signature$NL$(cat "$newest")$NL" ;; /dev/null|[Nn][Uu][Ll][Ll]) : ${resolv_conf_local_only:=NO} @@ -207,27 +222,44 @@ esac # Check if the file has actually changed or not if [ -e "$resolv_conf" ]; then - [ "$(cat "$resolv_conf")" = "$(printf %s "$newconf")" ] && exit 0 -fi - -# Change is good. -# If the old file does not have our signature, back it up. -# If the new file just has our signature, restore the backup. -if $backup; then - if [ "$newconf" = "$signature$NL" ]; then - if [ -e "$resolv_conf.bak" ]; then - newconf="$(cat "$resolv_conf.bak")$NL" - fi - elif [ -e "$resolv_conf" ]; then - read line <"$resolv_conf" - if [ "$line" != "$signature" ]; then - cp "$resolv_conf" "$resolv_conf.bak" + if [ "$CMD" != u ] && \ + [ "$(cat "$resolv_conf")" = "$(printf %s "$newconf")" ] + then + exit 0 + fi + read line <"$resolv_conf" + if [ "$line" != "$signature" ]; then + if [ "$CMD" != u ]; then + warn "signature mismatch: $resolv_conf" + warn "run \`resolvconf -u\` to update" + exit 1 fi + cp "$resolv_conf" "$resolv_conf.bak" fi fi -# Create our resolv.conf now -(umask 022; printf %s "$newconf" >"$resolv_conf") +# There are pros and cons for writing directly to resolv.conf +# instead of a temporary file and then moving it over. +# The default is to write to resolv.conf as it has the least +# issues and has been the long standing default behaviour. +# resolv.conf could also be bind mounted for network namespaces +# so we cannot move in this instance. +case "${resolv_conf_mv:-NO}" in +[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) + # Protect against symlink attack, ensure new file does not exist + rm -f "$resolv_conf_tmp" + # Keep original file owner, group and mode + [ -r "$resolv_conf" ] && cp -p "$resolv_conf" "$resolv_conf_tmp" + # Create our resolv.conf now + if (umask 022; printf %s "$newconf" >"$resolv_conf_tmp"); then + mv "$resolv_conf_tmp" "$resolv_conf" + fi + ;; +*) + (umask 022; printf %s "$newconf" >"$resolv_conf") + ;; +esac + if [ -n "$libc_restart" ]; then eval $libc_restart elif [ -n "$RESTARTCMD" ]; then diff --git a/mdnsd.in b/mdnsd.in new file mode 100644 index 000000000000..3b01cd459f16 --- /dev/null +++ b/mdnsd.in @@ -0,0 +1,32 @@ +#!/bin/sh +# Copyright (c) 2007-2023 Roy Marples +# All rights reserved + +# mdnsd notifier for resolvconf libc subscriber + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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 +# OWNER 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. + +: ${mdnsd_pidfile:=/var/run/mdnsd/mdnsd.pid} +if [ -s "$mdnsd_pidfile" ]; then + kill -HUP $(cat "$mdnsd_pidfile") +fi @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2007-2016 Roy Marples +# Copyright (c) 2007-2023 Roy Marples # All rights reserved # named subscriber for resolvconf @@ -79,7 +79,7 @@ for d in $DOMAINS; do done # Try to ensure that config dirs exist -if type config_mkdirs >/dev/null 2>&1; then +if command -v config_mkdirs >/dev/null 2>&1; then config_mkdirs "$named_options" "$named_zones" else @SBINDIR@/resolvconf -D "$named_options" "$named_zones" diff --git a/pdns_recursor.in b/pdns_recursor.in index f3632e000385..710456894652 100644 --- a/pdns_recursor.in +++ b/pdns_recursor.in @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2009-2019 Roy Marples +# Copyright (c) 2009-2023 Roy Marples # All rights reserved # PowerDNS Recursor subscriber for resolvconf @@ -54,7 +54,7 @@ for d in $DOMAINS; do done # Try to ensure that config dirs exist -if type config_mkdirs >/dev/null 2>&1; then +if command -v config_mkdirs >/dev/null 2>&1; then config_mkdirs "$pdnsd_zones" else @SBINDIR@/resolvconf -D "$pdnsd_zones" @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2010-2018 Roy Marples +# Copyright (c) 2010-2023 Roy Marples # All rights reserved # pdnsd subscriber for resolvconf @@ -46,7 +46,7 @@ remove_markers() in_marker=0 shift; shift - if type sed >/dev/null 2>&1; then + if command -v sed >/dev/null 2>&1; then sed "/^$m1/,/^$m2/d" $@ else for x do @@ -66,9 +66,9 @@ remove_markers() change_file() { if [ -e "$1" ]; then - if type cmp >/dev/null 2>&1; then + if command -v cmp >/dev/null 2>&1; then cmp -s "$1" "$2" - elif type diff >/dev/null 2>&1; then + elif command -v diff >/dev/null 2>&1; then diff -q "$1" "$2" >/dev/null else # Hopefully we're only working on small text files ... @@ -88,7 +88,7 @@ newresolv="# Generated by resolvconf$NL" changed=false # Try to ensure that config dirs exist -if type config_mkdirs >/dev/null 2>&1; then +if command -v config_mkdirs >/dev/null 2>&1; then config_mkdirs "$pdnsd_resolv" "$pdnsd_conf" else @SBINDIR@/resolvconf -D "$pdnsd_resolv" "$pdnsd_conf" diff --git a/resolvconf.8.in b/resolvconf.8.in index fa00582976d8..f29d272d71e6 100644 --- a/resolvconf.8.in +++ b/resolvconf.8.in @@ -1,4 +1,4 @@ -.\" Copyright (c) 2007-2016 Roy Marples +.\" Copyright (c) 2007-2025 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd November 29, 2016 +.Dd June 26, 2025 .Dt RESOLVCONF 8 .Os .Sh NAME @@ -35,14 +35,18 @@ .Op Fl m Ar metric .Op Fl p .Op Fl x -.Fl a Ar interface Ns Op Ar .protocol +.Fl a Ar key .No < Ns Pa file .Nm +.Fl C Ar pattern +.Nm +.Fl c Ar pattern +.Nm .Op Fl f -.Fl d Ar interface Ns Op Ar .protocol +.Fl d Ar key .Nm .Op Fl x -.Fl il Ar pattern +.Fl iLlp Ar pattern .Nm .Fl u .Nm @@ -68,12 +72,20 @@ file to via .Xr stdin 4 with the argument -.Fl a Ar interface Ns Op Ar .protocol +.Fl a Ar key instead of the filesystem. .Nm then updates .Pa /etc/resolv.conf as it thinks best. +If +.Pa /etc/resolv.conf +already exists and the top line does not match the expected signature, +then +.Nm +will refuse to update it unless the +.Fl u +update command is given. When a local resolver other than libc is installed, such as .Xr dnsmasq 8 or @@ -82,27 +94,40 @@ then .Nm will supply files that the resolver should be configured to include. .Pp +At it's heart, .Nm -assumes it has a job to do. -In some situations -.Nm -needs to act as a deterrent to writing to -.Pa /etc/resolv.conf . -Where this file cannot be made immutable or you just need to toggle this -behaviour, +is a key/value store for +.Pa resolv.conf +files. +Each entry must have a unique +.Ar key +and should be expressed as +.Sy interface.protocol +so that it's easy to tell from where the +.Pa resolv.conf +file came from. +This also allows using pattern matching such as +.Sy interface.* +to match all protocols running on the interface. +For example, a modern system will likely run DHCP, RA and DHCPv6 +which could be from separate programs or one program running +many protocols. +However, this is not a fixed requirement, .Nm -can be disabled by adding -.Sy resolvconf Ns = Ns NO -to -.Xr resolvconf.conf 5 . +will work with any key name and it should be treated as an opaque value +outside of +.Nm . .Pp .Nm -can mark an interfaces +can mark a .Pa resolv.conf -as private. +as private and optionally non-searchable. This means that the name servers listed in that .Pa resolv.conf -are only used for queries against the domain/search listed in the same file. +are only used for queries against the domain/search listed in the same file +and if non-searchable then the domain/search listed are +excluded from the global search list defined in +.Pa /etc/resolv.conf . This only works when a local resolver other than libc is installed. See .Xr resolvconf.conf 5 @@ -111,57 +136,97 @@ for how to configure to use a local name server and how to remove the private marking. .Pp .Nm -can mark an interfaces +can mark a .Pa resolv.conf as exclusive. -Only the latest exclusive interface is used for processing, otherwise all are. +Only the latest exclusive key is used for processing, otherwise all are. .Pp -When an interface goes down, it should then call +When a configuration source goes away, +such as an interface going down or a VPN stopping, +it should then call +.Nm +with +.Fl d Ar key +arguments to clean up the +.Pa resolv.conf +it added previously. +For systems that support the concept of persisting configuration when +the source is suspended, +such as the carrier going down, +then it should instead call .Nm with -.Fl d Ar interface.* -arguments to delete the +.Fl C Ar key +arguments to deprecate the entry +.Fl c Ar key +to activate the entry when it comes back again. +This only affects the order in which the .Pa resolv.conf -file(s) for all the -.Ar protocols -on the -.Ar interface . +entries are processed. .Pp -Here are some options for the above commands:- +Here are some options for the above commands: .Bl -tag -width pattern_opt .It Fl f -Ignore non existent interfaces. -Only really useful for deleting interfaces. +Ignore non existent +.Pa resolv.conf +entries. +Only really useful for deleting. .It Fl m Ar metric -Set the metric of the interface when adding it, default of 0. +Set the metric of the +.Pa resolv.conf +entry when adding it, default of 0. Lower metrics take precedence. -This affects the default order of interfaces when listed. -.It Fl p -Marks the interface +This affects the default order of entires when listed. +.It Fl p Op Ar pattern +Marks the .Pa resolv.conf -as private. +as private if the +.Fl a +command is given, otherwise +.Pa resolv.conf +entries having their key matching +.Ar pattern +are listed. +If an extra +.Fl p +is given then the +.Pa resolv.conf +is marked as non-searchable as well. .It Fl x -Mark the interface +Mark the .Pa resolv.conf -as exclusive when adding, otherwise only use the latest exclusive interface. +as exclusive when adding, otherwise only use the latest exclusive key. .El .Pp .Nm -has some more commands for general usage:- +has some more commands for general usage: .Bl -tag -width pattern_opt -.It Fl i Ar pattern -List the interfaces and protocols, optionally matching +.It Fl i Op Ar pattern +List the keys stored, optionally matching .Ar pattern , we have .Pa resolv.conf files for. -.It Fl l Ar pattern +If the +.Fl L +option is given first, then the keys will be list post-processed. +.It Fl L Op Ar pattern +List the +.Pa resolv.conf +files we have, +post-processed by the +.Xr resolvconf.conf 5 +configuration. +If +.Ar pattern +is specified then we list the files for the keys which match it. +.It Fl l Op Ar pattern List the .Pa resolv.conf files we have. If .Ar pattern -is specified then we list the files for the interfaces and protocols +is specified then we list the files for the keys which match it. that match it. .It Fl u Force @@ -169,27 +234,23 @@ Force to update all its subscribers. .Nm does not update the subscribers when adding a resolv.conf that matches -what it already has for that interface. +what it already has for that key. .It Fl Fl version Echo the resolvconf version to .Em stdout . .El .Pp .Nm -also has some commands designed to be used by it's subscribers and -system startup:- +also has some commands designed to be used by its subscribers and +system startup: .Bl -tag -width pattern_opt .It Fl I Initialise the state directory .Pa @VARDIR@ . -This only needs to be called if the initial system boot sequence does not -automatically clean it out; for example the state directory is moved -somewhere other than -.Pa /var/run . -If used, it should only be called once as early in the system boot sequence -as possible and before -.Nm -is used to add interfaces. +This should be called after the base state directory has either been +cleaned out or mounted as a memory backed filesystem during the +initial boot sequence before any daemon has the chance to call +.Nm . .It Fl R Echo the command used to restart a service. .It Fl r Ar service @@ -208,17 +269,18 @@ except that only the information configured in .Xr resolvconf.conf 5 is set. .El -.Sh INTERFACE ORDERING +.Sh RESOLV.CONF ORDERING For .Nm -to work effectively, it has to process the resolv.confs for the interfaces -in the correct order. +to work effectively, it has to process the +.Pa resolv.conf +entries in the correct order. .Nm -first processes interfaces from the -.Sy interface_order -list, then interfaces without a metic and that match the +first processes keys from the +.Sy key_order +list, then entries without a metric and that match the .Sy dynamic_order -list, then interfaces with a metric in order and finally the rest in +list, then entries with a metric in order and finally the rest in the operating systems lexical order. See .Xr resolvconf.conf 5 @@ -226,19 +288,9 @@ for details on these lists. .Sh PROTOCOLS Here are some suggested protocol tags to use for each .Pa resolv.conf -file registered on an -.Ar interface Ns No :- .Bl -tag -width pattern_opt .It dhcp Dynamic Host Configuration Protocol. -Initial versions of -.Nm -did not recommend a -.Ar protocol -tag be appended to the -.Ar interface -name. -When the protocol is absent, it is assumed to be the DHCP protocol. .It ppp Point-to-Point Protocol. .It ra @@ -270,11 +322,15 @@ option is not present then we use .Va IF_METRIC for the metric. .It Va IF_PRIVATE -Marks the interface +Marks the .Pa resolv.conf as private. +.It Va IF_NOSEARCH +Marks the +.Pa resolv.conf +as non-searchable. .It Va IF_EXCLUSIVE -Marks the interface +Marks the .Pa resolv.conf as exclusive. .El @@ -295,6 +351,17 @@ Directory of subscribers which are run after the libc subscriber is run. State directory for .Nm . .El +.Sh NOTES +Domain labels are assumed to be in ASCII and are converted to lower case +to avoid duplicate zones when given differing case from different sources. +.Pp +When running a local resolver other than libc, you will need to configure it +to include files that +.Nm +will generate. +You should consult +.Xr resolvconf.conf 5 +for instructions on how to configure your resolver. .Sh SEE ALSO .Xr resolver 3 , .Xr stdin 4 , @@ -310,14 +377,3 @@ resolvconf, as written by Thomas Hood. .Sh BUGS Please report them to .Lk http://roy.marples.name/projects/openresolv -.Pp -.Nm -does not validate any of the files given to it. -.Pp -When running a local resolver other than libc, you will need to configure it -to include files that -.Nm -will generate. -You should consult -.Xr resolvconf.conf 5 -for instructions on how to configure your resolver. diff --git a/resolvconf.conf.5.in b/resolvconf.conf.5.in index 1b0c47160845..4501221be84b 100644 --- a/resolvconf.conf.5.in +++ b/resolvconf.conf.5.in @@ -1,4 +1,4 @@ -.\" Copyright (c) 2009-2016 Roy Marples +.\" Copyright (c) 2009-2025 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd September 8, 2019 +.Dd May 15, 2025 .Dt RESOLVCONF.CONF 5 .Os .Sh NAME @@ -61,26 +61,44 @@ Set to NO to disable .Nm resolvconf from running any subscribers. Defaults to YES. -.It Sy interface_order -These interfaces will always be processed first. -If unset, defaults to the following:- -.Bd -compact -literal -offset indent +.It Sy allow_keys +If set, only these keys will be processed. +.It Sy deny_keys +If set, these keys will not be processed. +.It Sy exclude +Is a space separated list of key/value pairs to match. +If all key/value pairs in one element can be found in the file, +then the whole file will be excluded from processing. +The syntax is this: +.Va $keyword Ns / Ns Va $match Ns Op / Ns Va $keyword Ns / Ns Va $match +.Pp +For example given this configuration: +.Bd -literal -compact -offset indent +exclude="search/foo*/nameserver/1.2.3.4 search/bar.org" +.Ed +.Pp +Then any resolv.conf with both a search option starting with foo with a nameserver of 1.2.3.4 +OR a search option of bar.org would be excluded. +.It Sy key_order +These keys will always be processed first. +If unset, defaults to the following: +.Bd -literal -compact -offset indent lo lo[0-9]* .Ed .It Sy dynamic_order -These interfaces will be processed next, unless they have a metric. -If unset, defaults to the following:- -.Bd -compact -literal -offset indent -tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]* +These keys will be processed next, unless they have a metric. +If unset, defaults to the following: +.Bd -literal -compact -offset indent +tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]* .Ed -.It Sy inclusive_interfaces -Ignore any exclusive marking for these interfaces. +.It Sy inclusive_keys +Ignore any exclusive marking for these keys. This is handy when 3rd party integrations force the .Nm resolvconf -x option and you want to disable it easily. .It Sy local_nameservers -If unset, defaults to the following:- -.Bd -compact -literal -offset indent +If unset, defaults to the following: +.Bd -literal -compact -offset indent 127.* 0.0.0.0 255.255.255.255 ::1 .Ed .It Sy search_domains @@ -101,20 +119,25 @@ Append name servers to the dynamically generated list. A list of name servers to be removed from consideration. The default is 0.0.0.0 as some faulty routers send it via DHCP. To remove a block, you can use 192.168.* -.It Sy private_interfaces -These interfaces name servers will only be queried for the domains listed +.It Sy private_keys +These keys name servers will only be queried for the domains listed in their resolv.conf. Useful for VPN domains. Setting -.Sy private_interfaces Ns ="*" +.Sy private_keys Ns ="*" will stop the forwarding of the root zone and allows the local resolver to recursively query the root servers directly. Requires a local nameserver other than libc. This is equivalent to the .Nm resolvconf -p option. -.It Sy public_interfaces -Force these interface to be public, overriding the private marking. +.It Sy nosearch_keys +These keys domains/search won't be added to the global search list +in +.Pa /etc/resolv.conf . +.It Sy public_keys +Force these keys to be public, overriding the private and nosearch +markings. This is handy when 3rd party integrations force the .Nm resolvconf -p option and you want to disable it easily. @@ -124,24 +147,27 @@ The syntax is this: .Va $keyword Ns / Ns Va $match Ns / Ns Va $replacement .Pp Example, given this resolv.conf: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent domain foo.org search foo.org dead.beef nameserver 1.2.3.4 nameserver 2.3.4.5 .Ed -and this configuaration: -.Bd -compact -literal -offset indent +.Pp +and this configuration: +.Bd -literal -compact -offset indent replace="search/foo*/bar.com" replace="$replace nameserver/1.2.3.4/5.6.7.8" replace="$replace nameserver/2.3.4.5/" .Ed +.Pp you would get this resolv.conf instead: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent domain foo.org search bar.com nameserver 5.6.7.8 .Ed +.Pp .It Sy replace_sub Works the same way as .Sy replace @@ -152,11 +178,12 @@ Using the same example resolv.conf and changing to .Sy replace_sub , you would get this resolv.conf instead: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent domain foo.org search bar.com dead.beef nameserver 5.6.7.8 .Ed +.Pp .It Sy state_dir Override the default state directory of .Pa @VARDIR@ . @@ -167,12 +194,17 @@ is in use unless the old directory is copied to the new one. .Sh LIBC OPTIONS The following variables affect .Xr resolv.conf 5 -directly:- +directly: .Bl -tag -width indent .It Sy resolv_conf Defaults to .Pa /etc/resolv.conf if not set. +Set to +.Pa /dev/null +to stop +.Xr resolvconf 8 +from changing it. .It Sy resolv_conf_options A list of libc resolver options, as specified in .Xr resolv.conf 5 . @@ -188,6 +220,14 @@ is unset unless overridden and only the information set in .Nm is written to .Sy resolv_conf . +.It Sy resolv_conf_restore +When set to YES and +and an empty +.Pa resolv.conf +would be written, restore +.Pa resolv.conf.bak +instead if it exists. +Defaults to YES if not set. .It Sy resolv_conf_sortlist A libc resolver sortlist, as specified in .Xr resolv.conf 5 . @@ -206,21 +246,28 @@ Prepend name servers to the dynamically generated list. Append search domains to the dynamically generated list. .It Sy prepend_search Prepend search domains to the dynamically generated list. +.It Sy resolv_conf_mv +Defaults to NO. +Defines if +.Pa /etc/resolv.conf +is updated by writing to a temporary file and then moving it +vs writing directly to it. .El .Sh SUBSCRIBER OPTIONS openresolv ships with subscribers for the name servers .Xr dnsmasq 8 , .Xr named 8 , .Xr pdnsd 8 , -.Xr pdns_recursor 8 , +.Xr pdns_recursor 1 , and .Xr unbound 8 . Each subscriber can create configuration files which should be included in -in the subscribers main configuration file. +the subscribers main configuration file. .Pp -To disable a subscriber, simply set it's name to NO. +To disable a subscriber, simply set its name to NO. +If the subscriber name has a dash in it, then replace it with an underscore. For example, to disable the libc subscriber you would set: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent libc=NO .Ed .Bl -tag -width indent @@ -230,14 +277,14 @@ This file tells dnsmasq which name servers to use for specific domains. This file tells dnsmasq which name servers to use for global lookups. .Pp Example resolvconf.conf for dnsmasq: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent name_servers=127.0.0.1 dnsmasq_conf=/etc/dnsmasq-conf.conf dnsmasq_resolv=/etc/dnsmasq-resolv.conf .Ed .Pp Example dnsmasq.conf: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent listen-address=127.0.0.1 # If dnsmasq is compiled for DBus then we can take # advantage of not having to restart dnsmasq. @@ -253,14 +300,14 @@ Include this file in the named global scope, after the options block. This file tells named which name servers to use for specific domains. .Pp Example resolvconf.conf for named: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent name_servers=127.0.0.1 named_options=/etc/named-options.conf named_zones=/etc/named-zones.conf .Ed .Pp Example named.conf: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent options { listen-on { 127.0.0.1; }; include "/etc/named-options.conf"; @@ -281,14 +328,14 @@ If this variable is not set then it's written to .Pa pdnsd_conf . .Pp Example resolvconf.conf for pdnsd: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent name_servers=127.0.0.1 pdnsd_conf=/etc/pdnsd.conf # pdnsd_resolv=/etc/pdnsd-resolv.conf .Ed .Pp Example pdnsd.conf: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent global { server_ip = 127.0.0.1; status_ctl = on; @@ -304,29 +351,76 @@ server { This file tells pdns_recursor about specific and global name servers. .Pp Example resolvconf.conf for pdns_recursor: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent name_servers=127.0.0.1 pdns_zones=/etc/pdns/recursor-zones.conf .Ed .Pp Example recursor.conf: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent allow-from=127.0.0.0/8, ::1/128 forward-zones-file=/etc/pdns/recursor-zones.conf .Ed +.It Sy resolvectl +When set to YES, +.Xr resolvectl 1 +will be used to write per interface entries from +.Xr resolvconf 8 +to +.Xr systemd-resolved 8 . +A warning is emitted for any entry that cannot be matched to an +interface. +.Pp +This subscriber should only be used if your systemd-resolved does +not support DNS delegates and you need private or non searchable +.Xr resolvconf 8 +entries, or you're really beholden to seeing DNS setup per interface via +.Xr resolvectl 1 . +The systemd-resolved subscriber documented below is the better option. +.Pp +Example resolvconf.conf for resolvectl: +.Bd -literal -compact -offset indent +# Keep /etc/resolv.conf as systemd-resolved wants it +libc=NO +resolvectl=YES +.Ed +.It Sy systemd_resolved +When set to YES, global DNS will be written to the +.Sy systemd_resolved_conf +configuration file and DNS delegates will be written to the +.Sy systemd_delegate_dir +directory. +.It Sy systemd_resolved_conf +Defaults to +.Pa /run/systemd/resolved.conf.d/60-resolvconf.conf . +.It Sy systemd_delegate_dir +Defaults to +.Pa /run/systemd/dns-delegate.d . +.Pp +Example resolvconf.conf for systemd-resolved: +.Bd -literal -compact -offset indent +# Keep /etc/resolv.conf as systemd-resolved wants it +libc=NO +systemd_resolved=YES +.Ed .It Sy unbound_conf This file tells unbound about specific and global name servers. .It Sy unbound_insecure When set to YES, unbound marks the domains as insecure, thus ignoring DNSSEC. +.It Sy unbound_private +When set to YES, unbound marks the domains as private, allowing it and its subdomains to contain private addresses. +.It Sy unbound_forward_zone_options +Options appended to each forward zone. +Each option should be separated by an embedded new line. .Pp Example resolvconf.conf for unbound: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent name_servers=127.0.0.1 unbound_conf=/etc/unbound-resolvconf.conf .Ed .Pp Example unbound.conf: -.Bd -compact -literal -offset indent +.Bd -literal -compact -offset indent include: /etc/unbound-resolvconf.conf .Ed .El @@ -380,5 +474,13 @@ Location of the unbound pidfile. Each distribution is a special snowflake and likes to name the same thing differently, namely the named service script. .Pp +Swapping between resolvectl and systemd-resolved subscribers at runtime +is not supported. +Files referenced by systemd_resolved_conf and systemd_delegate_dir +need to be removed by hand. +A reboot is recommended so that stale data is removed. +While you could run them both at the same time, only using one is the +recommended approach. +.Pp Please report them to -.Lk http://roy.marples.name/projects/openresolv +.Lk https://roy.marples.name/projects/openresolv diff --git a/resolvconf.in b/resolvconf.in index e7d382111813..2f55a9de0eda 100644 --- a/resolvconf.in +++ b/resolvconf.in @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2007-2019 Roy Marples +# Copyright (c) 2007-2025 Roy Marples # All rights reserved # Redistribution and use in source and binary forms, with or without @@ -25,7 +25,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. RESOLVCONF="$0" -OPENRESOLV_VERSION="3.9.2" +OPENRESOLV_VERSION="3.17.4" SYSCONFDIR=@SYSCONFDIR@ LIBEXECDIR=@LIBEXECDIR@ VARDIR=@VARDIR@ @@ -34,7 +34,7 @@ RESTARTCMD=@RESTARTCMD@ if [ "$1" = "--version" ]; then echo "openresolv $OPENRESOLV_VERSION" - echo "Copyright (c) 2007-2016 Roy Marples" + echo "Copyright (c) 2007-2025 Roy Marples" exit 0 fi @@ -44,10 +44,31 @@ unset interface_order state_dir # If you change this, change the test in VFLAG and libc.in as well local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1" -dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*" +dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*" interface_order="lo lo[0-9]*" name_server_blacklist="0.0.0.0" +# Poor mans cat +# /usr might not be available +cat() +{ + OIFS="$IFS" + IFS='' + if [ -n "$1" ]; then + while read -r line; do + printf "%s\n" "$line" + done < "$1" + else + while read -r line; do + printf "%s\n" "$line" + done + fi + retval=$? + IFS="$OIFS" + return $retval +} + + # Support original resolvconf configuration layout # as well as the openresolv config file if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then @@ -59,21 +80,36 @@ elif [ -d "$SYSCONFDIR/resolvconf" ]; then interface_order="$(cat "$SYSCONFDIR"/interface-order)" fi fi -IFACEDIR="$VARDIR/interfaces" + +KEYDIR="$VARDIR/keys" METRICDIR="$VARDIR/metrics" PRIVATEDIR="$VARDIR/private" +NOSEARCHDIR="$VARDIR/nosearch" EXCLUSIVEDIR="$VARDIR/exclusive" +DEPRECATEDDIR="$VARDIR/deprecated" LOCKDIR="$VARDIR/lock" _PWD="$PWD" +# Compat +if [ ! -d "$KEYDIR" ] && [ -d "$VARDIR/interfaces" ]; then + KEYDIR="$VARDIR/interfaces" +fi +: ${allow_keys:="$allow_interfaces"} +: ${deny_keys:="$deny_interfaces"} +: ${key_order:="$interface_order"} +: ${inclusive_keys:="$inclusive_interfaces"} +: ${exclusive_keys:="$exclusive_interfaces"} +: ${private_keys:="$private_interfaces"} +: ${public_keys:="$public_interfaces"} + warn() { - echo "$*" >&2 + echo "${RESOLVCONF##*/}: $*" >&2 } error_exit() { - echo "$*" >&2 + warn "$*" exit 1 } @@ -85,24 +121,27 @@ usage() Inform the system about any DNS updates. Commands: - -a \$INTERFACE Add DNS information to the specified interface + -a \$KEY Add DNS information to the specified key (DNS supplied via stdin in resolv.conf format) - -d \$INTERFACE Delete DNS information from the specified interface + -C \$PATTERN Deprecate DNS information for matched key + -c \$PATTERN Configure DNS information for matched key + -d \$PATTERN Delete DNS information from the matched key -h Show this help cruft - -i [\$PATTERN] Show interfaces that have supplied DNS information - optionally from interfaces that match the specified + -i [\$PATTERN] Show keys that have supplied DNS information + optionally from keys that match the specified pattern - -l [\$PATTERN] Show DNS information, optionally from interfaces + -l [\$PATTERN] Show DNS information, optionally from keys that match the specified pattern + -L [\$PATTERN] Same as -l, but adjusted by our config -u Run updates from our current DNS information --version Echo the ${RESOLVCONF##*/} version Options: - -f Ignore non existent interfaces + -f Ignore non existent keys -m metric Give the added DNS information a metric - -p Mark the interface as private - -x Mark the interface as exclusive + -p Mark the resolv.conf as private + -x Mark the resolv.conf as exclusive Subscriber and System Init Commands: -I Init the state dir @@ -117,56 +156,111 @@ usage() EOF [ -z "$1" ] && exit 0 echo - error_exit "$*" + error_exit "$@" } -# Strip any trailing dot from each name as a FQDN does not belong -# in resolv.conf(5) -# If you think otherwise, capture a DNS trace and you'll see libc -# will strip it regardless. -# This also solves setting up duplicate zones in our subscribers. -# Also strip any comments denoted by #. -resolv_strip() -{ - space= - for word; do - case "$word" in - \#*) break;; +public_key() { + key="$1" + + # Allow expansion + cd "$KEYDIR" + + # Public keys override private ones. + for p in $public_keys; do + case "$key" in + "$p"|"$p":*) return 0;; esac - printf "%s%s" "$space${word%.}" - space=" " done - printf "\n" + + return 1 } -private_iface() +private_key() { - # Allow expansion - cd "$IFACEDIR" + key="$1" + + if public_key "$key"; then + return 1 + fi + + if [ -e "$PRIVATEDIR/$key" ]; then + return 0 + fi - # Public interfaces override private ones. - for p in $public_interfaces; do - case "$iface" in - "$p"|"$p":*) return 1;; + for p in $private_keys; do + case "$key" in + "$p"|"$p":*) return 0;; esac done - if [ -e "$PRIVATEDIR/$iface" ]; then + # Not a private key + return 1 +} + +nosearch_key() +{ + key="$1" + + if public_key "$key"; then + return 1 + fi + + if [ -e "$NOSEARCHDIR/$key" ]; then return 0 fi - - for p in $private_interfaces; do - case "$iface" in + + for p in $nosearch_keys; do + case "$key" in "$p"|"$p":*) return 0;; esac done - # Not a private interface + # Not a non searchable key + return 1 +} + +exclusive_key() +{ + key="$1" + + for x in "$EXCLUSIVEDIR/"*" $key"; do + if [ -f "$x" ]; then + return 0 + fi + done + + # Not an exclusive key return 1 } +# Quote input so it can be safely used for variable assignment via eval +quote() +{ + if [ -z "$1" ]; then + R="''" + else + R= + for W; do + while [ -n "$W" ]; do + case "$W" in + \'*) R="$R\\'"; W=${W#?};; + ?\'*) R="$R\\${W%%\'*}"; W="${W#?}";; + *\'*) R="$R'${W%%\'*}'"; W="'${W#*\'}";; + ?) R="$R\\$W"; W=;; + *) R="$R'$W'"; W=;; + esac + done + done + fi + + printf '%s\n' "$R" + return 0 +} + # Parse resolv.conf's and make variables # for domain name servers, search name servers and global nameservers +# Important! Each printf here should use the above quote function +# to ensure that user input is quoted for eval. parse_resolv() { domain= @@ -174,26 +268,32 @@ parse_resolv() newns= ns= private=false + nosearch=false search= while read -r line; do - stripped_line="$(resolv_strip ${line#* })" + value="${line#* }" case "$line" in "# resolv.conf from "*) if ${new}; then - iface="${line#\# resolv.conf from *}" + key="${line#\# resolv.conf from *}" new=false - if private_iface "$iface"; then + if nosearch_key "$key"; then + private=true + nosearch=true + elif private_key "$key"; then private=true + nosearch=false else private=false + nosearch=false fi fi ;; "nameserver "*) islocal=false for l in $local_nameservers; do - case "$stripped_line" in + case "$value" in $l) islocal=true break @@ -201,20 +301,22 @@ parse_resolv() esac done if $islocal; then - echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS $stripped_line\"" + printf 'LOCALNAMESERVERS="$LOCALNAMESERVERS "%s\n' "$(quote "$value")" else - ns="$ns$stripped_line " + ns="$ns${ns:+ }$value" fi ;; "domain "*) - search="$stripped_line" + search="$value" if [ -z "$domain" ]; then domain="$search" - echo "DOMAIN=\"$domain\"" + if ! $nosearch; then + printf 'DOMAIN=%s\n' "$(quote "$domain")" + fi fi ;; "search "*) - search="$stripped_line" + search="$value" ;; *) [ -n "$line" ] && continue @@ -227,11 +329,13 @@ parse_resolv() for d in $search; do ds="$ds${ds:+ }$d:$newns" done - echo "DOMAINS=\"\$DOMAINS $ds\"" + printf 'DOMAINS="$DOMAINS "%s\n' "$(quote "$ds")" + fi + if ! $nosearch; then + printf 'SEARCH="$SEARCH "%s\n' "$(quote "$search")" fi - echo "SEARCH=\"\$SEARCH $search\"" if ! $private; then - echo "NAMESERVERS=\"\$NAMESERVERS $ns\"" + printf 'NAMESERVERS="$NAMESERVERS "%s\n' "$(quote "$ns")" fi ns= search= @@ -274,26 +378,21 @@ dirname() config_mkdirs() { - e=0 for f; do [ -n "$f" ] || continue d="$(dirname "$f")" if [ ! -d "$d" ]; then - if type install >/dev/null 2>&1; then - install -d "$d" || e=$? - else - mkdir "$d" || e=$? - fi + mkdir -p "$d" || return $? fi done - return $e + return 0 } # With the advent of alternative init systems, it's possible to have # more than one installed. So we need to try and guess what one we're -# using unless overriden by configure. +# using unless overridden by configure. # Note that restarting a service is a last resort - the subscribers -# should make a reasonable attempt to reconfigre the service via some +# should make a reasonable attempt to reconfigure the service via some # method, normally SIGHUP. detect_init() { @@ -328,6 +427,12 @@ detect_init() then /usr/sbin/invoke-rc.d $1 restart fi' + elif [ -x /usr/bin/s6-rc ] && [ -x /usr/bin/s6-svc ]; then + RESTARTCMD=' + if s6-rc -a list 2>/dev/null | grep -qFx $1-srv + then + s6-svc -r /run/service/$1-srv + fi' elif [ -x /sbin/service ]; then # Old RedHat RCDIR=/etc/init.d @@ -368,6 +473,8 @@ detect_init() then /etc/rc.d/$1 restart fi' + elif [ -d /etc/dinit.d ] && command -v dinitctl >/dev/null 2>&1; then + RESTARTCMD='dinitctl --quiet restart --ignore-unstarted $1' else for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do [ -d $x ] || continue @@ -395,9 +502,9 @@ echo_resolv() { OIFS="$IFS" - [ -n "$1" ] && [ -f "$IFACEDIR/$1" ] || return 1 + [ -n "$1" ] && [ -f "$KEYDIR/$1" ] || return 1 echo "# resolv.conf from $1" - # Our variable maker works of the fact each resolv.conf per interface + # Our variable maker works of the fact each resolv.conf per key # is separated by blank lines. # So we remove them when echoing them. while read -r line; do @@ -407,103 +514,296 @@ echo_resolv() IFS='' printf "%s\n" "$line" fi - done < "$IFACEDIR/$1" + done < "$KEYDIR/$1" IFS="$OIFS" } -list_resolv() +deprecated_key() +{ + [ -d "$DEPRECATEDDIR" ] || return 1 + + cd "$DEPRECATEDDIR" + for da; do + for daf in *; do + [ -f "$daf" ] || continue + case "$da" in + $daf) return 0;; + esac + done + done + return 1 +} + +match() { - [ -d "$IFACEDIR" ] || return 0 + match="$1" + file="$2" + retval=1 + count=0 - cmd="$1" + while read -r keyword value; do + new_match= + for om in $match; do + m="$om" + keep= + while [ -n "$m" ]; do + k="${m%%/*}" + r="${m#*/}" + f="${r%%/*}" + r="${r#*/}" + # If the length of m is the same as k/f then + # we know that we are done + if [ ${#m} = $((${#k} + 1 + ${#f})) ]; then + r= + fi + m="$r" + matched=false + case "$keyword" in + $k) + case "$value" in + $f) + matched=true + ;; + esac + ;; + esac + if ! $matched; then + keep="$keep${keep:+/}$k/$f" + fi + done + if [ -n "$om" ] && [ -z "$keep" ]; then + retval=0 + break 2 + fi + new_match="${new_match}${new_match:+ }${keep}" + done + match="${new_match}" + done < "$file" + return $retval +} + +list_keys() { + list_cmd="$1" shift - excl=false + + [ -d "$KEYDIR" ] || return 0 + cd "$KEYDIR" + + [ -n "$1" ] || set -- "*" list= - report=false retval=0 + if [ "$list_cmd" = -i ] || [ "$list_cmd" = -l ]; then + for i in $@; do + if [ ! -f "$i" ]; then + if ! $force && [ "$i" != "*" ]; then + echo "No resolv.conf for key $i" >&2 + fi + retval=2 + continue + fi + list="$list $i" + done + [ -z "$list" ] || uniqify $list + return $retval + fi - case "$IF_EXCLUSIVE" in - [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) - excl=true - if [ -d "$EXCLUSIVEDIR" ]; then - cd "$EXCLUSIVEDIR" - for i in *; do - if [ -f "$i" ]; then - list="${i#* }" - break + if [ "$list_cmd" != -I ] && [ "$list_cmd" != -L ]; then + echo "list_keys: unknown command $list_cmd" >&2 + return 1 + fi + + if [ -d "$EXCLUSIVEDIR" ]; then + cd "$EXCLUSIVEDIR" + for i in $EXCLUSIVEDIR/*; do + if [ -f "$i" ]; then + cd "$KEYDIR" + for ii in $inclusive_keys; do + if [ -f "$ii" ] && [ "${i#* }" = "$ii" ]; then + continue 2 + fi + done + list="${i#* }" + break + fi + done + cd "$KEYDIR" + if [ -n "$list" ]; then + for i in $@; do + # list will be one item due to the above + if [ -f "$i" ] && [ "$i" = "$list" ]; then + echo "$i" + return 0 fi done + return 0 fi - cd "$IFACEDIR" - for i in $inclusive_interfaces; do - if [ -f "$i" ] && [ "$list" = "$i" ]; then - list= - excl=false - break + fi + + for i in $key_order; do + for ii in "$i" "$i":* "$i".*; do + [ -f "$ii" ] && list="$list $ii" + done + done + + for i in $dynamic_order; do + for ii in "$i" "$i":* "$i".*; do + if [ -f "$ii" ] && ! [ -e "$METRICDIR/"*" $ii" ] + then + list="$list $ii" fi done - ;; - esac + done - # If we have an interface ordering list, then use that. - # It works by just using pathname expansion in the interface directory. - if [ -n "$1" ]; then - list="$*" - $force || report=true - elif ! $excl; then - cd "$IFACEDIR" - for i in $interface_order; do - [ -f "$i" ] && list="$list $i" - for ii in "$i":* "$i".*; do - [ -f "$ii" ] && list="$list $ii" - done + # Interfaces have an implicit metric of 0 if not specified. + for i in *; do + if [ -f "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then + list="$list $i" + fi + done + + if [ -d "$METRICDIR" ]; then + cd "$METRICDIR" + for i in *; do + [ -f "$i" ] && list="$list ${i#* }" done - for i in $dynamic_order; do - if [ -e "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then - list="$list $i" + cd "$KEYDIR" + fi + + # Move deprecated keys to the back + active= + deprecated= + for i in $list; do + if deprecated_key "$i"; then + deprecated="$deprecated $i" + else + active="$active $i" + fi + done + list="$active $deprecated" + + retval=0 + if [ "$1" != "*" ]; then + cd "$KEYDIR" + matched= + for i in $@; do + if ! [ -f "$i" ]; then + if ! $force; then + echo "No resolv.conf for key $i" >&2 + fi + retval=2 + continue fi - for ii in "$i":* "$i".*; do - if [ -f "$ii" ] && ! [ -e "$METRICDIR/"*" $ii" ] - then - list="$list $ii" + for ii in $list; do + if [ "$i" = "$ii" ]; then + matched="$matched${matched:+ }$i" + break fi done done - # Interfaces have an implicit metric of 0 if not specified. - for i in *; do - if [ -f "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then - list="$list $i" - fi - done - if [ -d "$METRICDIR" ]; then - cd "$METRICDIR" - for i in *; do - [ -f "$i" ] && list="$list ${i#* }" - done + if [ -z "$matched" ]; then + return $retval fi + list="$matched" fi - cd "$IFACEDIR" - retval=1 + allowed= for i in $(uniqify $list); do - # Only list interfaces which we really have - if ! [ -f "$i" ]; then - if $report; then - echo "No resolv.conf for interface $i" >&2 - retval=2 + if [ -n "$allow_keys" ]; then + x=false + for ii in $allow_keys; do + if [ "$i" = "$ii" ]; then + x=true + break + fi + done + $x || continue + fi + for ii in $deny_keys; do + if [ "$i" = "$ii" ]; then + continue 2 fi + done + + if [ -n "$exclude" ] && match "$exclude" "$i"; then continue fi - - if [ "$cmd" = i ] || [ "$cmd" = "-i" ]; then - printf %s "$i " - else - echo_resolv "$i" && echo + allowed="$allowed${allowed:+ }$i" + done + + cd "$KEYDIR" + for i in $exclusive_keys; do + for ii in $allowed; do + if [ "$i" = "$ii" ]; then + echo "$i" + return + fi + done + done + [ -z "$allowed" ] || echo "$allowed" +} + +list_resolv() +{ + keys="$(list_keys "$@")" + retval=$? + if [ "$retval" != 0 ]; then + return $retval + fi + for i in $keys; do + echo_resolv "$i" && echo + done +} + +list_private() +{ + KEYS= + cd "$KEYDIR" + if [ -z "$1" ]; then + set -- "*" + fi + for i in $@; do + if private_key "$i"; then + KEYS="${KEYS}${KEYS:+ }$i" fi - [ $? = 0 ] && [ "$retval" = 1 ] && retval=0 done - [ "$cmd" = i ] || [ "$cmd" = "-i" ] && echo - return $retval + if [ -n "$KEYS" ]; then + echo "$KEYS" + fi +} + +list_nosearch() +{ + + KEYS= + cd "$KEYDIR" + if [ -z "$1" ]; then + set -- "*" + fi + for i in $@; do + if nosearch_key "$i"; then + KEYS="${KEYS}${KEYS:+ }$i" + fi + done + if [ -n "$KEYS" ]; then + echo "$KEYS" + fi +} + +list_exclusive() +{ + KEYS= + cd "$KEYDIR" + if [ -z "$1" ]; then + set -- "*" + fi + for i in $@; do + if exclusive_key "$i"; then + KEYS="${KEYS}${KEYS:+ }$i" + fi + done + if [ -n "$KEYS" ]; then + echo "$KEYS" + fi } list_remove() @@ -558,7 +858,81 @@ echo_append() echo } -replace() +tolower() { + # There is no good way of doing this portably in shell :( + # Luckily we are only doing this for domain names which we + # know have to be ASCII. + # Non ASCII domains *should* be translated to ASCII *before* + # we get to this stage. + # We could use echo "$@" | tr '[:upper:]' '[:lower:]' but + # tr is in /usr/bin and may not be available when data is fed + # to resolvconf. + # So it's the cost of a pipe + fork vs this slow loop + # + for word; do + # Check if we have any upper to avoid looping per char + case "$word" in + *[A-Z]*) ;; + *) printf "%s " "$word"; continue;; + esac + + while [ -n "$word" ]; do + # Remove everything except the first character + afterchar="${word#?}" + # Remove the afterchar to get the first character + char="${word%%$afterchar}" + # Assign afterchar back to word for looping + word="$afterchar" + + # Now enforce lowercase a-z + case "$char" in + A) char=a;; + B) char=b;; + C) char=c;; + D) char=d;; + E) char=e;; + F) char=f;; + G) char=g;; + H) char=h;; + I) char=i;; + J) char=j;; + K) char=k;; + L) char=l;; + M) char=m;; + N) char=n;; + O) char=o;; + P) char=p;; + Q) char=q;; + R) char=r;; + S) char=s;; + T) char=t;; + U) char=u;; + V) char=v;; + W) char=w;; + X) char=x;; + Y) char=y;; + Z) char=z;; + esac + printf %s "$char" + done + printf " " + done + printf "\n" +} + +# Strip any trailing dot from each name as a FQDN does not belong +# in resolv.conf(5). +# While DNS is not case sensitive, our labels for building the zones +# are, so ensure it's lower case. +process_domain() +{ + for word in $(tolower "$@"); do + printf "%s " "${word%.}" + done + printf "\n" +} + +process_resolv() { while read -r keyword value; do for r in $replace; do @@ -593,6 +967,18 @@ replace() done val="$val${val:+ }$sub" done + case "$keyword" in + \#) + case "$val" in + "resolv.conf from "*) ;; + *) continue;; + esac + ;; + \#*) continue;; + esac + case "$keyword" in + domain|search) val="$(process_domain $val)";; + esac printf "%s %s\n" "$keyword" "$val" done } @@ -605,14 +991,12 @@ make_vars() SEARCH= NAMESERVERS= LOCALNAMESERVERS= - + if [ -n "${name_servers}${search_domains}" ]; then eval "$(echo_prepend | parse_resolv)" fi if [ -z "$VFLAG" ]; then - IF_EXCLUSIVE=1 - list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0 - eval "$(list_resolv -l "$@" | replace | parse_resolv)" + eval "$(list_resolv -L "$@" | process_resolv | parse_resolv)" fi if [ -n "${name_servers_append}${search_domains_append}" ]; then eval "$(echo_append | parse_resolv)" @@ -646,6 +1030,7 @@ make_vars() newdomains="$newdomains${newdomains:+ }$dn:$newns" fi done + DOMAIN="$(list_remove domain_blacklist $DOMAIN)" SEARCH="$(uniqify $SEARCH)" SEARCH="$(list_remove domain_blacklist $SEARCH)" @@ -653,21 +1038,30 @@ make_vars() NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)" LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)" LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)" - echo "DOMAIN='$DOMAIN'" - echo "SEARCH='$SEARCH'" - echo "NAMESERVERS='$NAMESERVERS'" - echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'" - echo "DOMAINS='$newdomains'" + + # Ensure output is quoted for eval + printf 'DOMAIN=%s\n' "$(quote "$DOMAIN")" + printf 'SEARCH=%s\n' "$(quote "$SEARCH")" + printf 'NAMESERVERS=%s\n' "$(quote "$NAMESERVERS")" + printf 'LOCALNAMESERVERS=%s\n' "$(quote "$LOCALNAMESERVERS")" + printf 'DOMAINS=%s\n' "$(quote "$newdomains")" } force=false +LFLAG= VFLAG= -while getopts a:Dd:fhIilm:pRruvVx OPT; do +while getopts a:C:c:Dd:fhIiLlm:pRruvVx OPT; do case "$OPT" in f) force=true;; h) usage;; m) IF_METRIC="$OPTARG";; - p) IF_PRIVATE=1;; + p) + if [ "$IF_PRIVATE" = 1 ]; then + IF_NOSEARCH=1 + else + IF_PRIVATE=1 + fi + ;; V) VFLAG=1 if [ "$local_nameservers" = \ @@ -677,19 +1071,23 @@ while getopts a:Dd:fhIilm:pRruvVx OPT; do fi ;; x) IF_EXCLUSIVE=1;; - '?') ;; - *) cmd="$OPT"; iface="$OPTARG";; + '?') exit 1;; + *) + [ "$OPT" != L ] || LFLAG=1 + cmd="$OPT"; key="$OPTARG";; esac done shift $(($OPTIND - 1)) -args="$iface${iface:+ }$*" +if [ -n "$key" ]; then + set -- "$key" "$@" +fi -# -I inits the state dir -if [ "$cmd" = I ]; then - if [ -d "$VARDIR" ]; then - rm -rf "$VARDIR"/* +if [ -z "$cmd" ]; then + if [ "$IF_PRIVATE" = 1 ]; then + cmd=p + elif [ "$IF_EXCLUSIVE" = 1 ]; then + cmd=x fi - exit $? fi # -D ensures that the listed config file base dirs exist @@ -698,9 +1096,38 @@ if [ "$cmd" = D ]; then exit $? fi -# -l lists our resolv files, optionally for a specific interface -if [ "$cmd" = l ] || [ "$cmd" = i ]; then - list_resolv "$cmd" "$args" +# -i lists which keys have a resolv file +if [ "$cmd" = i ]; then + # If the -L modifier is given, the list is post-processed + if [ "$LFLAG" = 1 ]; then + cmd="L" + fi + list_keys "-$cmd" "$@" + exit $? +fi + +# -l lists our resolv files, optionally for a specific key +if [ "$cmd" = l ]; then + list_resolv "-$cmd" "$@" + exit $? +fi +# -L is the same as -l, but post-processed from our config +if [ "$cmd" = L ]; then + list_resolv "-$cmd" "$@" | process_resolv + exit $? +fi + +if [ "$cmd" = p ]; then + if [ "$IF_NOSEARCH" = 1 ]; then + list_nosearch "$@" + else + list_private "$@" + fi + exit $? +fi + +if [ "$cmd" = x ]; then + list_exclusive "$@" exit $? fi @@ -708,7 +1135,6 @@ fi if [ "$cmd" = r ] || [ "$cmd" = R ]; then detect_init || exit 1 if [ "$cmd" = r ]; then - set -- $args eval "$RESTARTCMD" else echo "$RESTARTCMD" | @@ -719,30 +1145,36 @@ fi # Not normally needed, but subscribers should be able to run independently if [ "$cmd" = v ] || [ -n "$VFLAG" ]; then - make_vars "$iface" + make_vars "$@" exit $? fi # Test that we have valid options -if [ "$cmd" = a ] || [ "$cmd" = d ]; then - if [ -z "$iface" ]; then - usage "Interface not specified" +case "$cmd" in +a|d|C|c) + if [ -z "$key" ]; then + error_exit "Key not specified" + fi + ;; +I|u) ;; +*) + if [ -n "$cmd" ] && [ "$cmd" != h ]; then + error_exit "Unknown option $cmd" fi -elif [ "$cmd" != u ]; then - [ -n "$cmd" ] && [ "$cmd" != h ] && usage "Unknown option $cmd" usage -fi + ;; +esac if [ "$cmd" = a ]; then - for x in '/' \\ ' ' '*'; do - case "$iface" in - *[$x]*) error_exit "$x not allowed in interface name";; + for x in '/' '\' ' ' '*'; do + case "$key" in + "$x"|"$x"*|*"$x"|*"$x"*) error_exit "$x not allowed in key name";; esac done for x in '.' '-' '~'; do - case "$iface" in - [$x]*) error_exit \ - "$x not allowed at start of interface name";; + case "$key" in + "$x"*) error_exit \ + "$x not allowed at start of key name";; esac done [ "$cmd" = a ] && [ -t 0 ] && error_exit "No file given via stdin" @@ -765,15 +1197,15 @@ if [ ! -d "$VARDIR" ]; then fi fi -if [ ! -d "$IFACEDIR" ]; then - mkdir -m 0755 -p "$IFACEDIR" || \ - error_exit "Failed to create needed directory $IFACEDIR" +if [ ! -d "$KEYDIR" ]; then + mkdir -m 0755 -p "$KEYDIR" || \ + error_exit "Failed to create needed directory $KEYDIR" if [ "$cmd" = d ]; then # Provide the same error messages as below if ! ${force}; then - cd "$IFACEDIR" - for i in $args; do - warn "No resolv.conf for interface $i" + cd "$KEYDIR" + for i in $@; do + warn "No resolv.conf for key $i" done fi ${force} @@ -781,7 +1213,7 @@ if [ ! -d "$IFACEDIR" ]; then fi fi -# An interface was added, changed, deleted or a general update was called. +# A key was added, changed, deleted or a general update was called. # Due to exclusivity we need to ensure that this is an atomic operation. # Our subscribers *may* need this as well if the init system is sub par. # As such we spinlock at this point as best we can. @@ -789,6 +1221,9 @@ fi # in /usr which we do our very best to operate without. [ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR" : ${lock_timeout:=10} +: ${clear_nopids:=5} +have_pid=false +had_pid=false while true; do if mkdir "$LOCKDIR" 2>/dev/null; then trap 'rm -rf "$LOCKDIR";' EXIT @@ -796,18 +1231,43 @@ while true; do echo $$ >"$LOCKDIR/pid" break fi - pid=$(cat "$LOCKDIR/pid") - if ! kill -0 "$pid"; then + pid=$(cat "$LOCKDIR/pid" 2>/dev/null) + if [ "$pid" -gt 0 ] 2>/dev/null; then + have_pid=true + had_pid=true + else + have_pid=false + clear_nopids=$(($clear_nopids - 1)) + if [ "$clear_nopids" -le 0 ]; then + warn "not seen a pid, clearing lock directory" + rm -rf "$LOCKDIR" + else + lock_timeout=$(($lock_timeout - 1)) + sleep 1 + fi + continue + fi + if $have_pid && ! kill -0 "$pid"; then warn "clearing stale lock pid $pid" rm -rf "$LOCKDIR" continue fi lock_timeout=$(($lock_timeout - 1)) if [ "$lock_timeout" -le 0 ]; then - error_exit "timed out waiting for lock from pid $pid" + if $have_pid; then + error_exit "timed out waiting for lock from pid $pid" + else + if $had_pid; then + error_exit "timed out waiting for lock" \ + "from some pids" + else + error_exit "timed out waiting for lock" + fi + fi fi sleep 1 done +unset have_pid had_pid clear_nopids case "$cmd" in a) @@ -816,9 +1276,9 @@ a) changed=false changedfile=false # If what we are given matches what we have, then do nothing - if [ -e "$IFACEDIR/$iface" ]; then + if [ -e "$KEYDIR/$key" ]; then if [ "$(echo "$resolv")" != \ - "$(cat "$IFACEDIR/$iface")" ] + "$(cat "$KEYDIR/$key")" ] then changed=true changedfile=true @@ -828,21 +1288,21 @@ a) changedfile=true fi - # Set metric and private before creating the interface resolv.conf file + # Set metric and private before creating the resolv.conf file # to ensure that it will have the correct flags [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR" - oldmetric="$METRICDIR/"*" $iface" + oldmetric="$METRICDIR/"*" $key" newmetric= if [ -n "$IF_METRIC" ]; then # Pad metric to 6 characters, so 5 is less than 10 while [ ${#IF_METRIC} -le 6 ]; do IF_METRIC="0$IF_METRIC" done - newmetric="$METRICDIR/$IF_METRIC $iface" + newmetric="$METRICDIR/$IF_METRIC $key" fi - rm -f "$METRICDIR/"*" $iface" + rm -f "$METRICDIR/"*" $key" [ "$oldmetric" != "$newmetric" ] && - [ "$oldmetric" != "$METRICDIR/* $iface" ] && + [ "$oldmetric" != "$METRICDIR/* $key" ] && changed=true [ -n "$newmetric" ] && echo " " >"$newmetric" @@ -852,19 +1312,37 @@ a) [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR" mkdir "$PRIVATEDIR" fi - [ -e "$PRIVATEDIR/$iface" ] || changed=true - [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface" + [ -e "$PRIVATEDIR/$key" ] || changed=true + [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$key" ;; *) - if [ -e "$PRIVATEDIR/$iface" ]; then - rm -f "$PRIVATEDIR/$iface" + if [ -e "$PRIVATEDIR/$key" ]; then + rm -f "$PRIVATEDIR/$key" changed=true fi ;; esac + case "$IF_NOSEARCH" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) + if [ ! -d "$NOSEARCHDIR" ]; then + [ -e "$NOSEARCHDIR" ] && rm "$NOSEARCHDIR" + mkdir "$NOSEARCHDIR" + fi + [ -e "$NOSEARCHDIR/$key" ] || changed=true + [ -d "$NOSEARCHDIR" ] && echo " " >"$NOSEARCHDIR/$key" + ;; + *) + if [ -e "$NOSEARCHDIR/$key" ]; then + rm -f "$NOSEARCHDIR/$key" + changed=true + fi + ;; + esac + set +x + oldexcl= - for x in "$EXCLUSIVEDIR/"*" $iface"; do + for x in "$EXCLUSIVEDIR/"*" $key"; do if [ -f "$x" ]; then oldexcl="$x" break @@ -880,7 +1358,7 @@ a) for x in *; do [ -f "$x" ] && break done - if [ "${x#* }" != "$iface" ]; then + if [ "${x#* }" != "$key" ]; then if [ "$x" = "${x% *}" ]; then x=10000000 else @@ -892,7 +1370,7 @@ a) x=$(($x - 1)) fi if [ -d "$EXCLUSIVEDIR" ]; then - echo " " >"$EXCLUSIVEDIR/$x $iface" + echo " " >"$EXCLUSIVEDIR/$x $key" fi changed=true fi @@ -906,34 +1384,80 @@ a) esac if $changedfile; then - printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $? - elif ! $changed; then + printf "%s\n" "$resolv" >"$KEYDIR/$key" || exit $? + elif ! $changed && [ ! -e "$VARDIR"/error ]; then exit 0 fi unset changed changedfile oldmetric newmetric x oldexcl ;; d) - # Delete any existing information about the interface - cd "$IFACEDIR" + # Delete any existing information about the key + cd "$KEYDIR" changed=false - for i in $args; do + for i in $@; do if [ -e "$i" ]; then changed=true elif ! ${force}; then - warn "No resolv.conf for interface $i" + warn "No resolv.conf for key $i" fi rm -f "$i" "$METRICDIR/"*" $i" \ "$PRIVATEDIR/$i" \ "$EXCLUSIVEDIR/"*" $i" || exit $? done - if ! ${changed}; then + + if ! $changed && [ ! -e "$VARDIR"/error ]; then # Set the return code based on the forced flag - ${force} + $force exit $? fi unset changed i ;; + +C) + # Mark key as deprecated + [ ! -d "$DEPRECATEDDIR" ] && mkdir "$DEPRECATEDDIR" + cd "$DEPRECATEDDIR" + changed=false + for i in $@; do + if [ ! -e "$i" ]; then + changed=true + echo " " >"$i" || exit $? + fi + done + if ! $changed && [ ! -e "$VARDIR"/error ]; then + exit 0 + fi + unset changed i + ;; + +c) + # Mark key as active + if [ -d "$DEPRECATEDDIR" ]; then + cd "$DEPRECATEDDIR" + changed=false + for i in $@; do + if [ -e "$i" ]; then + changed=true + rm "$i" || exit $? + fi + done + if ! $changed && [ ! -e "$VARDIR"/error ]; then + exit 0 + fi + unset changed i + fi + ;; +I) + # Init the state dir, keeping our lock and key directories only + for i in "$VARDIR"/*; do + case "$i" in + "$LOCKDIR") ;; + "$KEYDIR") rm -rf "$KEYDIR"/*;; + *) rm -rf "$i";; + esac + done + ;; esac case "${resolvconf:-YES}" in @@ -947,7 +1471,7 @@ export RESTARTCMD RCDIR _NOINIT_WARNED eval "$(make_vars)" export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS -: ${list_resolv:=list_resolv -l} +: ${list_resolv:=list_resolv -L} retval=0 # Run scripts in the same directory resolvconf is run from @@ -955,17 +1479,26 @@ retval=0 cd "$_PWD" for script in "$LIBEXECDIR"/*; do if [ -f "$script" ]; then - eval script_enabled="\$${script##*/}" + script_var="${script##*/}" + while [ "${script_var%%-*}" != "$script_var" ]; do + script_var="${script_var%%-*}_${script_var#*-}" + done + eval script_enabled="\$$script_var" case "${script_enabled:-YES}" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;; *) continue;; esac if [ -x "$script" ]; then - "$script" "$cmd" "$iface" + "$script" "$cmd" "$key" else - (set -- "$cmd" "$iface"; . "$script") + (set -- "$cmd" "$key"; . "$script") fi retval=$(($retval + $?)) fi done +if [ "$retval" = 0 ]; then + rm -f "$VARDIR"/error +else + echo "$retval" >"$VARDIR"/error +fi exit $retval diff --git a/resolvectl.in b/resolvectl.in new file mode 100644 index 000000000000..167aac62ba57 --- /dev/null +++ b/resolvectl.in @@ -0,0 +1,159 @@ +#!/bin/sh +# Copyright (c) 2025 Roy Marples +# All rights reserved + +# resolvectl subscriber for resolvconf + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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 +# OWNER 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. + +[ -f "@SYSCONFDIR@"/resolvconf.conf ] || exit 0 +. "@SYSCONFDIR@/resolvconf.conf" || exit 1 + +case "${resolvectl:-NO}" in +[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;; +*) exit 0;; +esac + +# If we don't have resolvectl or systemd-resolved isn't running then +# we can't do much. +# We can't persist our data in /run/systemd/resolve/netif/$ifindex +# because systemd-resolved keeps it somehow, ie we can't change it +# once we have inserted it +if ! [ -d /sys/class/net ] || \ + ! type resolvectl >/dev/null 2>&1 || \ + ! pidof systemd-resolved >/dev/null +then + exit 1 +fi + +# resolvectl only accepts resolv.conf setup per physical interface +# although resolvconf has always hinted that the named configuration +# should be $interface.$protocol, this has never been a fixed requirement. +# Because resolvectl only accepts one configuration per interface we need +# to try and merge the resolv.conf's together. +# Luckily resolvconf makes this easy for us. + +# Returns a list of resolvconf entries for a real interface +get_resolvconf_interfaces() { + IFACE="$1" + [ -d /sys/class/net/"$IFACE" ] || return 1 + + IFACES= + for IFACE_PROTO in $(@SBINDIR@/resolvconf -Li "$IFACE" "$IFACE.*" 2>/dev/null); do + # ens5 will work with ens5.dhcp and ens5.ra, + # but not ens5.5 or ens5.5.dhcp + if [ "$IFACE_PROTO" != "$IFACE" ]; then + # Ensure that ens5.5.dhcp doesn't work for ens5 + if [ "${IFACE_PROTO%.*}" != "$IFACE" ]; then + continue + fi + # Ensure that ens5.dhcp isn't a real interface + # as ens5.5 likely is and the .5 matches the .dhcp + if [ -d /sys/class/net/"$IFACE_PROTO" ]; then + continue + fi + fi + IFACES="$IFACES${IFACES:+ }$IFACE_PROTO" + done + echo "$IFACES" +} + +# For the given interface, apply a list of resolvconf entries +apply_resolvconf() { + IFACE="$1" + shift + + if [ -z "$1" ]; then + resolvectl revert "$IFACE" + return + fi + + # Set the default-route property first to avoid leakage. + # If any entry is private, the whole interface has to be private. + # If a more granular approach is needed, consider using the + # systemd-resolved subscriber instead which supports DNS delegates. + if [ -n "$(@SBINDIR@/resolvconf -p $@)" ]; then + resolvectl default-route "$IFACE" false + else + resolvectl default-route "$IFACE" true + fi + + # Now set domain and dns + DOMAIN=$(@SBINDIR@/resolvconf -L $@ 2>/dev/null | sed -n -e "s/domain //p" -e "s/search //p") + NS=$(@SBINDIR@/resolvconf -L $@ 2>/dev/null | sed -n -e "s/nameserver //p") + if [ -n "$DOMAIN" ]; then + # If any entry is marked as not searchable, we mark all the + # domains as non searchable. + # If a more granular approach is needed, consider using the + # systemd-resolved subscriber instead which supports DNS delegates. + if [ -n "$(@SBINDIR@/resolvconf -pp $@)" ]; then + ND= + for d in $DOMAIN; do + ND="$ND${ND:+ }~$d" + done + DOMAIN="$ND" + fi + resolvectl domain "$IFACE" $DOMAIN + else + resolvectl domain "$IFACE" "" + fi + if [ -n "$NS" ]; then + resolvectl dns "$IFACE" $NS + else + resolvectl dns "$IFACE" "" + fi +} + +# To get the full features of resolvconf, we need to work out each interface +# for every resolvconf addition and deletion +# This is because resolvconf.conf might have changed OR an exclusive +# interface deleted which makes other interfaces visible. +cd /sys/class/net +for IFACE in *; do + if [ "$IFACE" = lo ]; then + # systemd-resolved doesn't work with lo + continue + fi + + IFACES=$(get_resolvconf_interfaces "$IFACE") + apply_resolvconf "$IFACE" $IFACES +done + +# warn about resolv.conf with no matching interface +FAILED= +for IFACE_PROTO in $(@SBINDIR@/resolvconf -Li); do + IFACE="${IFACE_PROTO%.*}" + if [ "$IFACE" = lo ]; then + # Don't warn about loopback interface as that is typically + # used to configure libc for a nameserver on it and the libc + # subscriber will process that just fine. + continue + fi + + if ! [ -d "/sys/class/net/$IFACE" ]; then + FAILED="$FAILED${FAILED:+ }$IFACE_PROTO" + fi +done +if [ -n "$FAILED" ]; then + echo "Could not apply resolv.conf to resolvectl: $FAILED" >&2 +fi diff --git a/systemd-resolved.in b/systemd-resolved.in new file mode 100644 index 000000000000..0190a73723e5 --- /dev/null +++ b/systemd-resolved.in @@ -0,0 +1,96 @@ +#!/bin/sh +# Copyright (c) 2025 Roy Marples +# All rights reserved + +# systemd-resolved subscriber for resolvconf + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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 +# OWNER 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. + +[ -f "@SYSCONFDIR@"/resolvconf.conf ] || exit 0 +. "@SYSCONFDIR@/resolvconf.conf" || exit 1 + +case "${systemd_resolved:-NO}" in +[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;; +*) exit 0;; +esac + +[ -n "$RESOLVCONF" ] || eval "$(@SBINDIR@/resolvconf -v)" +NL=" +" + +: ${systemd_resolved_conf:=/run/systemd/resolved.conf.d/60-resolvconf.conf} +: ${systemd_delegate_dir:=/run/systemd/dns-delegate.d} + +# Try to ensure that config dirs exist +if command -v config_mkdirs >/dev/null 2>&1; then + config_mkdirs "$systemd_resolved_conf" "$systemd_delegate_dir/x" +else + @SBINDIR@/resolvconf -D "$systemd_resolved_conf" "$systemd_delegate_dir/x" +fi + +header="# Generated by resolvconf$NL" +header="${header}$NL" +header="${header}[Resolve]$NL" + +conf="$header" +# We emit blank values to force them to reset on SIGHUP +conf="${conf}DNS=$NAMESERVERS$NL" +# Indicate these nameservers are for all domain lookups by using ~. +conf="${conf}Domains=$SEARCH${NAMESERVERS:+ ~.}$NL" + +printf %s "$conf" >"$systemd_resolved_conf" + +# DNS Delegates requires https://github.com/systemd/systemd/pull/34368 +rm -f "$systemd_delegate_dir/resolvconf-"*".dns-delegate" +header="# Generated by resolvconf$NL" +header="${header}$NL" +header="${header}[Delegate]$NL" +for d in $DOMAINS; do + dn="${d%%:*}" + ns="${d#*:}" + dconf="${header}Domains=" + search=false + for sd in $SEARCH; do + if [ "$sd" = "$dn" ]; then + search=true + break + fi + done + if ! $search; then + dconf="${dconf}~" + fi + dconf="${dconf}$dn$NL" + dconf="${dconf}DNS=" + while [ -n "$ns" ]; do + dconf="$dconf${ns%%,*} " + [ "$ns" = "${ns#*,}" ] && break + ns="${ns#*,}" + done + dconf="$dconf$NL" + printf %s "$dconf" >"$systemd_delegate_dir/resolvconf-$dn.dns-delegate" +done + +pid=$(pidof systemd-resolved) +if [ -n "$pid" ]; then + kill -HUP $pid +fi diff --git a/unbound.in b/unbound.in index 34cb401f57c1..a61a09d1935c 100644 --- a/unbound.in +++ b/unbound.in @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2009-2016 Roy Marples +# Copyright (c) 2009-2023 Roy Marples # All rights reserved # unbound subscriber for resolvconf @@ -27,6 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. unbound_insecure= +unbound_private= [ -f "@SYSCONFDIR@"/resolvconf.conf ] || exit 0 . "@SYSCONFDIR@/resolvconf.conf" || exit 1 @@ -42,13 +43,29 @@ newconf="# Generated by resolvconf$NL" for d in $DOMAINS; do dn="${d%%:*}" ns="${d#*:}" + create_unbound_insecure=false + create_unbound_private=false case "$unbound_insecure" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) - newconf="$newconf${NL}server:$NL" - newconf="$newconf domain-insecure: \"$dn\"$NL" - ;; + create_unbound_insecure=true ;; + esac + case "$unbound_private" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) + create_unbound_private=true ;; esac + if $create_unbound_insecure || $create_unbound_private; then + newconf="$newconf${NL}server:$NL" + if $create_unbound_insecure; then + newconf="$newconf domain-insecure: \"$dn\"$NL" + fi + if $create_unbound_private; then + newconf="$newconf private-domain: \"$dn\"$NL" + fi + fi newconf="$newconf${NL}forward-zone:$NL name: \"$dn\"$NL" + if [ -n "$unbound_forward_zone_options" ]; then + newconf="$newconf $unbound_forward_zone_options${NL}" + fi while [ -n "$ns" ]; do newconf="$newconf forward-addr: ${ns%%,*}$NL" [ "$ns" = "${ns#*,}" ] && break @@ -58,13 +75,16 @@ done if [ -n "$NAMESERVERS" ]; then newconf="$newconf${NL}forward-zone:$NL name: \".\"$NL" + if [ -n "$unbound_forward_zone_options" ]; then + newconf="$newconf $unbound_forward_zone_options${NL}" + fi for n in $NAMESERVERS; do newconf="$newconf forward-addr: $n$NL" done fi # Try to ensure that config dirs exist -if type config_mkdirs >/dev/null 2>&1; then +if command -v config_mkdirs >/dev/null 2>&1; then config_mkdirs "$unbound_conf" else @SBINDIR@/resolvconf -D "$unbound_conf" |
