1219732Sume#!/bin/sh
2296190Spfg# Copyright (c) 2007-2016 Roy Marples
3219732Sume# All rights reserved
4219732Sume
5219732Sume# Redistribution and use in source and binary forms, with or without
6219732Sume# modification, are permitted provided that the following conditions
7219732Sume# are met:
8219732Sume#     * Redistributions of source code must retain the above copyright
9219732Sume#       notice, this list of conditions and the following disclaimer.
10219732Sume#     * Redistributions in binary form must reproduce the above
11219732Sume#       copyright notice, this list of conditions and the following
12219732Sume#       disclaimer in the documentation and/or other materials provided
13219732Sume#       with the distribution.
14219732Sume#
15219732Sume# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16219732Sume# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17219732Sume# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18219732Sume# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19219732Sume# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20219732Sume# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21219732Sume# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22219732Sume# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23219732Sume# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24219732Sume# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25219732Sume# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26219732Sume
27219732SumeRESOLVCONF="$0"
28313980SpfgOPENRESOLV_VERSION="3.9.0"
29219732SumeSYSCONFDIR=@SYSCONFDIR@
30219732SumeLIBEXECDIR=@LIBEXECDIR@
31219732SumeVARDIR=@VARDIR@
32304515SpfgRCDIR=@RCDIR@
33304515SpfgRESTARTCMD=@RESTARTCMD@
34282434Sgjb
35313980Spfgif [ "$1" = "--version" ]; then
36313980Spfg	echo "openresolv $OPENRESOLV_VERSION"
37313980Spfg	echo "Copyright (c) 2007-2016 Roy Marples"
38313980Spfg	exit 0
39313980Spfgfi
40313980Spfg
41282434Sgjb# Disregard dhcpcd setting
42282434Sgjbunset interface_order state_dir
43282434Sgjb
44282434Sgjb# If you change this, change the test in VFLAG and libc.in as well
45282434Sgjblocal_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
46282434Sgjb
47282434Sgjbdynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*"
48282434Sgjbinterface_order="lo lo[0-9]*"
49282434Sgjbname_server_blacklist="0.0.0.0"
50282434Sgjb
51219732Sume# Support original resolvconf configuration layout
52219732Sume# as well as the openresolv config file
53219732Sumeif [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
54219732Sume	. "$SYSCONFDIR"/resolvconf.conf
55219732Sume	[ -n "$state_dir" ] && VARDIR="$state_dir"
56219732Sumeelif [ -d "$SYSCONFDIR/resolvconf" ]; then
57219732Sume	SYSCONFDIR="$SYSCONFDIR/resolvconf"
58219732Sume	if [ -f "$SYSCONFDIR"/interface-order ]; then
59219732Sume		interface_order="$(cat "$SYSCONFDIR"/interface-order)"
60219732Sume	fi
61219732Sumefi
62219732SumeIFACEDIR="$VARDIR/interfaces"
63219732SumeMETRICDIR="$VARDIR/metrics"
64219732SumePRIVATEDIR="$VARDIR/private"
65282434SgjbEXCLUSIVEDIR="$VARDIR/exclusive"
66282434SgjbLOCKDIR="$VARDIR/lock"
67296190Spfg_PWD="$PWD"
68219732Sume
69282434Sgjbwarn()
70282434Sgjb{
71282434Sgjb	echo "$*" >&2
72282434Sgjb}
73219732Sume
74219732Sumeerror_exit()
75219732Sume{
76219732Sume	echo "$*" >&2
77219732Sume	exit 1
78219732Sume}
79219732Sume
80219732Sumeusage()
81219732Sume{
82219732Sume	cat <<-EOF
83304515Spfg	Usage: ${RESOLVCONF##*/} [options] command [argument]
84219732Sume
85219732Sume	Inform the system about any DNS updates.
86219732Sume
87304515Spfg	Commands:
88219732Sume	  -a \$INTERFACE    Add DNS information to the specified interface
89219732Sume	                   (DNS supplied via stdin in resolv.conf format)
90304515Spfg	  -d \$INTERFACE    Delete DNS information from the specified interface
91304515Spfg	  -h               Show this help cruft
92304515Spfg	  -i [\$PATTERN]    Show interfaces that have supplied DNS information
93304515Spfg                   optionally from interfaces that match the specified
94304515Spfg                   pattern
95304515Spfg	  -l [\$PATTERN]    Show DNS information, optionally from interfaces
96304515Spfg	                   that match the specified pattern
97304515Spfg
98304515Spfg	  -u               Run updates from our current DNS information
99313980Spfg	  --version        Echo the ${RESOLVCONF##*/} version
100304515Spfg
101304515Spfg	Options:
102304515Spfg	  -f               Ignore non existent interfaces
103219732Sume	  -m metric        Give the added DNS information a metric
104219732Sume	  -p               Mark the interface as private
105282434Sgjb	  -x               Mark the interface as exclusive
106304515Spfg
107304515Spfg	Subscriber and System Init Commands:
108219732Sume	  -I               Init the state dir
109304515Spfg	  -r \$SERVICE      Restart the system service
110304515Spfg	                   (restarting a non-existent or non-running service
111304515Spfg	                    should have no output and return 0)
112304515Spfg	  -R               Show the system service restart command
113219732Sume	  -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
114219732Sume	  		   the console
115304515Spfg	  -V [\$PATTERN]    Same as -v, but only uses configuration in
116304515Spfg	                   $SYSCONFDIR/resolvconf.conf
117219732Sume	EOF
118219732Sume	[ -z "$1" ] && exit 0
119219732Sume	echo
120219732Sume	error_exit "$*"
121219732Sume}
122219732Sume
123296190Spfg# Strip any trailing dot from each name as a FQDN does not belong
124296190Spfg# in resolv.conf(5)
125296190Spfg# If you think otherwise, capture a DNS trace and you'll see libc
126296190Spfg# will strip it regardless.
127296190Spfg# This also solves setting up duplicate zones in our subscribers.
128296190Spfgstrip_trailing_dots()
129296190Spfg{
130296351Spfg	local n= d=
131296190Spfg
132296190Spfg	for n; do
133296351Spfg		printf "$d%s" "${n%.}"
134296351Spfg		d=" "
135296190Spfg	done
136296190Spfg	printf "\n"
137296190Spfg}
138296190Spfg
139313980Spfgprivate_iface()
140313980Spfg{
141313980Spfg	local p
142313980Spfg
143313980Spfg	# Allow expansion
144313980Spfg	cd "$IFACEDIR"
145313980Spfg
146313980Spfg	# Public interfaces override private ones.
147313980Spfg	for p in $public_interfaces; do
148313980Spfg		case "$iface" in
149313980Spfg		"$p"|"$p":*) return 1;;
150313980Spfg		esac
151313980Spfg	done
152313980Spfg
153313980Spfg	if [ -e "$PRIVATEDIR/$iface" ]; then
154313980Spfg		return 0
155313980Spfg	fi
156313980Spfg	
157313980Spfg	for p in $private_interfaces; do
158313980Spfg		case "$iface" in
159313980Spfg		"$p"|"$p":*) return 0;;
160313980Spfg		esac
161313980Spfg	done
162313980Spfg
163313980Spfg	# Not a private interface
164313980Spfg	return 1
165313980Spfg}
166313980Spfg
167219732Sume# Parse resolv.conf's and make variables
168219732Sume# for domain name servers, search name servers and global nameservers
169219732Sumeparse_resolv()
170219732Sume{
171219732Sume	local line= ns= ds= search= d= n= newns=
172282434Sgjb	local new=true iface= private=false p= domain= l= islocal=
173219732Sume
174219732Sume	newns=
175219732Sume
176282434Sgjb	while read -r line; do
177219732Sume		case "$line" in
178219732Sume		"# resolv.conf from "*)
179219732Sume			if ${new}; then
180219732Sume				iface="${line#\# resolv.conf from *}"
181219732Sume				new=false
182313980Spfg				if private_iface "$iface"; then
183219732Sume					private=true
184219732Sume				else
185219732Sume					private=false
186219732Sume				fi
187219732Sume			fi
188219732Sume			;;
189219732Sume		"nameserver "*)
190282434Sgjb			islocal=false
191282434Sgjb			for l in $local_nameservers; do
192282434Sgjb				case "${line#* }" in
193282434Sgjb				$l)
194282434Sgjb					islocal=true
195282434Sgjb					echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\""
196282434Sgjb					break
197282434Sgjb					;;
198282434Sgjb				esac
199282434Sgjb			done
200282434Sgjb			$islocal || ns="$ns${line#* } "
201219732Sume			;;
202282434Sgjb		"domain "*)
203296190Spfg			search="$(strip_trailing_dots ${line#* })"
204282434Sgjb			if [ -z "$domain" ]; then
205296190Spfg				domain="$search"
206282434Sgjb				echo "DOMAIN=\"$domain\""
207282434Sgjb			fi
208219732Sume			;;
209282434Sgjb		"search "*)
210296190Spfg			search="$(strip_trailing_dots ${line#* })"
211282434Sgjb			;;
212219732Sume		*)
213219732Sume			[ -n "$line" ] && continue
214219732Sume			if [ -n "$ns" -a -n "$search" ]; then
215219732Sume				newns=
216219732Sume				for n in $ns; do
217219732Sume					newns="$newns${newns:+,}$n"
218219732Sume				done
219219732Sume				ds=
220219732Sume				for d in $search; do
221219732Sume					ds="$ds${ds:+ }$d:$newns"
222219732Sume				done
223219732Sume				echo "DOMAINS=\"\$DOMAINS $ds\""
224219732Sume			fi
225219732Sume			echo "SEARCH=\"\$SEARCH $search\""
226219732Sume			if ! $private; then
227219732Sume				echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
228219732Sume			fi
229219732Sume			ns=
230219732Sume			search=
231219732Sume			new=true
232219732Sume			;;
233219732Sume		esac
234219732Sume	done
235219732Sume}
236219732Sume
237219732Sumeuniqify()
238219732Sume{
239219732Sume	local result=
240219732Sume	while [ -n "$1" ]; do
241219732Sume		case " $result " in
242219732Sume		*" $1 "*);;
243219732Sume		*) result="$result $1";;
244219732Sume		esac
245219732Sume		shift
246219732Sume	done
247219732Sume	echo "${result# *}"
248219732Sume}
249219732Sume
250282434Sgjbdirname()
251282434Sgjb{
252282434Sgjb	local dir= OIFS="$IFS"
253282434Sgjb	local IFS=/
254282434Sgjb	set -- $@
255282434Sgjb	IFS="$OIFS"
256282434Sgjb	if [ -n "$1" ]; then
257282434Sgjb		printf %s .
258282434Sgjb	else
259282434Sgjb		shift
260282434Sgjb	fi
261282434Sgjb	while [ -n "$2" ]; do
262282434Sgjb		printf "/%s" "$1"
263282434Sgjb		shift
264282434Sgjb	done
265282434Sgjb	printf "\n"
266282434Sgjb}
267282434Sgjb
268282434Sgjbconfig_mkdirs()
269282434Sgjb{
270282434Sgjb	local e=0 f d
271282434Sgjb	for f; do
272282434Sgjb		[ -n "$f" ] || continue
273282434Sgjb		d="$(dirname "$f")"
274282434Sgjb		if [ ! -d "$d" ]; then
275282434Sgjb			if type install >/dev/null 2>&1; then
276282434Sgjb				install -d "$d" || e=$?
277282434Sgjb			else
278282434Sgjb				mkdir "$d" || e=$?
279282434Sgjb			fi
280282434Sgjb		fi
281282434Sgjb	done
282282434Sgjb	return $e
283282434Sgjb}
284282434Sgjb
285304515Spfg# With the advent of alternative init systems, it's possible to have
286304515Spfg# more than one installed. So we need to try and guess what one we're
287304515Spfg# using unless overriden by configure.
288304515Spfg# Note that restarting a service is a last resort - the subscribers
289304515Spfg# should make a reasonable attempt to reconfigre the service via some
290304515Spfg# method, normally SIGHUP.
291304515Spfgdetect_init()
292304515Spfg{
293304515Spfg	[ -n "$RESTARTCMD" ] && return 0
294304515Spfg
295304515Spfg	# Detect the running init system.
296304515Spfg	# As systemd and OpenRC can be installed on top of legacy init
297304515Spfg	# systems we try to detect them first.
298304515Spfg	local status="@STATUSARG@"
299304515Spfg	: ${status:=status}
300304515Spfg	if [ -x /bin/systemctl -a -S /run/systemd/private ]; then
301304515Spfg		RESTARTCMD="if /bin/systemctl --quiet is-active \$1.service; then
302304515Spfg	/bin/systemctl restart \$1.service;
303304515Spfgfi"
304304515Spfg	elif [ -x /usr/bin/systemctl -a -S /run/systemd/private ]; then
305304515Spfg		RESTARTCMD="if /usr/bin/systemctl --quiet is-active \$1.service; then
306304515Spfg	/usr/bin/systemctl restart \$1.service;
307304515Spfgfi"
308304515Spfg	elif [ -x /sbin/rc-service -a \
309304515Spfg	    -s /libexec/rc/init.d/softlevel -o -s /run/openrc/softlevel ]
310304515Spfg	then
311304515Spfg		RESTARTCMD="/sbin/rc-service -i \$1 -- -Ds restart"
312304515Spfg	elif [ -x /usr/sbin/invoke-rc.d ]; then
313304515Spfg		RCDIR=/etc/init.d
314304515Spfg		RESTARTCMD="if /usr/sbin/invoke-rc.d --quiet \$1 status 1>/dev/null 2>&1; then
315304515Spfg	/usr/sbin/invoke-rc.d \$1 restart;
316304515Spfgfi"
317304515Spfg	elif [ -x /sbin/service ]; then
318304515Spfg		# Old RedHat
319304515Spfg		RCDIR=/etc/init.d
320304515Spfg		RESTARTCMD="if /sbin/service \$1; then
321304515Spfg	/sbin/service \$1 restart;
322304515Spfgfi"
323304515Spfg	elif [ -x /usr/sbin/service ]; then
324304515Spfg		# Could be FreeBSD
325304515Spfg		RESTARTCMD="if /usr/sbin/service \$1 $status 1>/dev/null 2>&1; then
326304515Spfg	/usr/sbin/service \$1 restart;
327304515Spfgfi"
328304515Spfg	elif [ -x /bin/sv ]; then
329313980Spfg		RESTARTCMD="/bin/sv status \$1 >/dev/null 2>&1 && /bin/sv try-restart \$1"
330304515Spfg	elif [ -x /usr/bin/sv ]; then
331313980Spfg		RESTARTCMD="/usr/bin/sv status \$1 >/dev/null 2>&1 && /usr/bin/sv try-restart \$1"
332304515Spfg	elif [ -e /etc/arch-release -a -d /etc/rc.d ]; then
333304515Spfg		RCDIR=/etc/rc.d
334304515Spfg		RESTARTCMD="if [ -e /var/run/daemons/\$1 ]; then
335304515Spfg	/etc/rc.d/\$1 restart;
336304515Spfgfi"
337304515Spfg	elif [ -e /etc/slackware-version -a -d /etc/rc.d ]; then
338304515Spfg		RESTARTCMD="if /etc/rc.d/rc.\$1 status 1>/dev/null 2>&1; then
339304515Spfg	/etc/rc.d/rc.\$1 restart;
340304515Spfgfi"
341304515Spfg	elif [ -e /etc/rc.d/rc.subr -a -d /etc/rc.d ]; then
342304515Spfg		# OpenBSD
343304515Spfg		RESTARTCMD="if /etc/rc.d/\$1 check 1>/dev/null 2>&1; then
344304515Spfg	/etc/rc.d/\$1 restart;
345304515Spfgfi"
346304515Spfg	else
347304515Spfg		for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
348304515Spfg			[ -d $x ] || continue
349304515Spfg			RESTARTCMD="if $x/\$1 $status 1>/dev/null 2>&1; then
350304515Spfg	$x/\$1 restart;
351304515Spfgfi"
352304515Spfg			break
353304515Spfg		done
354304515Spfg	fi
355304515Spfg
356304515Spfg	if [ -z "$RESTARTCMD" ]; then
357304515Spfg		if [ "$NOINIT_WARNED" != true ]; then
358304515Spfg			warn "could not detect a useable init system"
359304515Spfg			_NOINIT_WARNED=true
360304515Spfg		fi
361304515Spfg		return 1
362304515Spfg	fi
363304515Spfg	_NOINIT_WARNED=
364304515Spfg	return 0
365304515Spfg}
366304515Spfg
367304515Spfgecho_resolv()
368304515Spfg{
369304515Spfg	local line= OIFS="$IFS"
370304515Spfg
371304515Spfg	[ -n "$1" -a -f "$IFACEDIR/$1" ] || return 1
372304515Spfg	echo "# resolv.conf from $1"
373304515Spfg	# Our variable maker works of the fact each resolv.conf per interface
374304515Spfg	# is separated by blank lines.
375304515Spfg	# So we remove them when echoing them.
376304515Spfg	while read -r line; do
377304515Spfg		IFS="$OIFS"
378304515Spfg		if [ -n "$line" ]; then
379304515Spfg			# We need to set IFS here to preserve any whitespace
380304515Spfg			IFS=''
381304515Spfg			printf "%s\n" "$line"
382304515Spfg		fi
383304515Spfg	done < "$IFACEDIR/$1"
384304515Spfg	IFS="$OIFS"
385304515Spfg}
386304515Spfg
387219732Sumelist_resolv()
388219732Sume{
389219732Sume	[ -d "$IFACEDIR" ] || return 0
390219732Sume
391282434Sgjb	local report=false list= retval=0 cmd="$1" excl=
392219732Sume	shift
393219732Sume
394282434Sgjb	case "$IF_EXCLUSIVE" in
395282434Sgjb	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
396282434Sgjb		if [ -d "$EXCLUSIVEDIR" ]; then
397282434Sgjb			cd "$EXCLUSIVEDIR"
398282434Sgjb			for i in *; do
399282434Sgjb				if [ -f "$i" ]; then
400282434Sgjb					list="${i#* }"
401282434Sgjb					break
402282434Sgjb				fi
403282434Sgjb			done
404282434Sgjb		fi
405282434Sgjb		excl=true
406313980Spfg		cd "$IFACEDIR"
407313980Spfg		for i in $inclusive_interfaces; do
408313980Spfg			if [ -f "$i" -a "$list" = "$i" ]; then
409313980Spfg				list=
410313980Spfg				excl=false
411313980Spfg				break
412313980Spfg			fi
413313980Spfg		done
414282434Sgjb		;;
415282434Sgjb	*)
416282434Sgjb		excl=false
417282434Sgjb		;;
418282434Sgjb	esac
419282434Sgjb
420219732Sume	# If we have an interface ordering list, then use that.
421219732Sume	# It works by just using pathname expansion in the interface directory.
422219732Sume	if [ -n "$1" ]; then
423282434Sgjb		list="$*"
424219732Sume		$force || report=true
425282434Sgjb	elif ! $excl; then
426219732Sume		cd "$IFACEDIR"
427219732Sume		for i in $interface_order; do
428282434Sgjb			[ -f "$i" ] && list="$list $i"
429282434Sgjb			for ii in "$i":* "$i".*; do
430282434Sgjb				[ -f "$ii" ] && list="$list $ii"
431282434Sgjb			done
432219732Sume		done
433219732Sume		for i in $dynamic_order; do
434219732Sume			if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
435219732Sume				list="$list $i"
436219732Sume			fi
437282434Sgjb			for ii in "$i":* "$i".*; do
438282434Sgjb				if [ -f "$ii" -a ! -e "$METRICDIR/"*" $ii" ]; then
439282434Sgjb					list="$list $ii"
440282434Sgjb				fi
441282434Sgjb			done
442219732Sume		done
443219732Sume		if [ -d "$METRICDIR" ]; then
444219732Sume			cd "$METRICDIR"
445219732Sume			for i in *; do
446282434Sgjb				[ -f "$i" ] && list="$list ${i#* }"
447219732Sume			done
448219732Sume		fi
449219732Sume		list="$list *"
450219732Sume	fi
451219732Sume
452219732Sume	cd "$IFACEDIR"
453282434Sgjb	retval=1
454219732Sume	for i in $(uniqify $list); do
455219732Sume		# Only list interfaces which we really have
456282434Sgjb		if ! [ -f "$i" ]; then
457219732Sume			if $report; then
458219732Sume				echo "No resolv.conf for interface $i" >&2
459282434Sgjb				retval=2
460219732Sume			fi
461219732Sume			continue
462219732Sume		fi
463219732Sume		
464219732Sume		if [ "$cmd" = i -o "$cmd" = "-i" ]; then
465282434Sgjb			printf %s "$i "
466219732Sume		else
467313980Spfg			echo_resolv "$i" && echo
468219732Sume		fi
469282434Sgjb		[ $? = 0 -a "$retval" = 1 ] && retval=0
470219732Sume	done
471219732Sume	[ "$cmd" = i -o "$cmd" = "-i" ] && echo
472219732Sume	return $retval
473219732Sume}
474219732Sume
475282434Sgjblist_remove() {
476282434Sgjb	local list= e= l= result= found= retval=0
477282434Sgjb
478282434Sgjb	[ -z "$2" ] && return 0
479282434Sgjb	eval list=\"\$$1\"
480282434Sgjb	shift
481282434Sgjb
482282434Sgjb	set -f
483282434Sgjb	for e; do
484282434Sgjb		found=false
485282434Sgjb		for l in $list; do
486282434Sgjb			case "$e" in
487282434Sgjb			$l) found=true;;
488282434Sgjb			esac
489282434Sgjb			$found && break
490282434Sgjb		done
491282434Sgjb		if $found; then
492282434Sgjb			retval=$(($retval + 1))
493282434Sgjb		else
494282434Sgjb			result="$result $e"
495282434Sgjb		fi
496282434Sgjb	done
497282434Sgjb	set +f
498282434Sgjb	echo "${result# *}"
499282434Sgjb	return $retval
500282434Sgjb}
501282434Sgjb
502282434Sgjbecho_prepend()
503282434Sgjb{
504282434Sgjb	echo "# Generated by resolvconf"
505282434Sgjb	if [ -n "$search_domains" ]; then
506282434Sgjb		echo "search $search_domains"
507282434Sgjb	fi
508282434Sgjb	for n in $name_servers; do
509282434Sgjb		echo "nameserver $n"
510282434Sgjb	done
511282434Sgjb	echo
512282434Sgjb}
513282434Sgjb
514282434Sgjbecho_append()
515282434Sgjb{
516282434Sgjb	echo "# Generated by resolvconf"
517282434Sgjb	if [ -n "$search_domains_append" ]; then
518282434Sgjb		echo "search $search_domains_append"
519282434Sgjb	fi
520282434Sgjb	for n in $name_servers_append; do
521282434Sgjb		echo "nameserver $n"
522282434Sgjb	done
523282434Sgjb	echo
524282434Sgjb}
525282434Sgjb
526282434Sgjbreplace()
527282434Sgjb{
528282434Sgjb	local r= k= f= v= val= sub=
529282434Sgjb
530282434Sgjb	while read -r keyword value; do
531282434Sgjb		for r in $replace; do
532282434Sgjb			k="${r%%/*}"
533282434Sgjb			r="${r#*/}"
534282434Sgjb			f="${r%%/*}"
535282434Sgjb			r="${r#*/}"
536282434Sgjb			v="${r%%/*}"
537282434Sgjb			case "$keyword" in
538282434Sgjb			$k)
539282434Sgjb				case "$value" in
540282434Sgjb				$f) value="$v";;
541282434Sgjb				esac
542282434Sgjb				;;
543282434Sgjb			esac
544282434Sgjb		done
545282434Sgjb		val=
546282434Sgjb		for sub in $value; do
547282434Sgjb			for r in $replace_sub; do
548282434Sgjb				k="${r%%/*}"
549282434Sgjb				r="${r#*/}"
550282434Sgjb				f="${r%%/*}"
551282434Sgjb				r="${r#*/}"
552282434Sgjb				v="${r%%/*}"
553282434Sgjb				case "$keyword" in
554282434Sgjb				$k)
555282434Sgjb					case "$sub" in
556282434Sgjb					$f) sub="$v";;
557282434Sgjb					esac
558282434Sgjb					;;
559282434Sgjb				esac
560282434Sgjb			done
561282434Sgjb			val="$val${val:+ }$sub"
562282434Sgjb		done
563282434Sgjb		printf "%s %s\n" "$keyword" "$val"
564282434Sgjb	done
565282434Sgjb}
566282434Sgjb
567219732Sumemake_vars()
568219732Sume{
569282434Sgjb	local newdomains= d= dn= newns= ns=
570219732Sume
571282434Sgjb	# Clear variables
572282434Sgjb	DOMAIN=
573282434Sgjb	DOMAINS=
574282434Sgjb	SEARCH=
575282434Sgjb	NAMESERVERS=
576282434Sgjb	LOCALNAMESERVERS=
577282434Sgjb	
578282434Sgjb	if [ -n "$name_servers" -o -n "$search_domains" ]; then
579282434Sgjb		eval "$(echo_prepend | parse_resolv)"
580282434Sgjb	fi
581282434Sgjb	if [ -z "$VFLAG" ]; then
582282434Sgjb		IF_EXCLUSIVE=1
583282434Sgjb		list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
584282434Sgjb		eval "$(list_resolv -l "$@" | replace | parse_resolv)"
585282434Sgjb	fi
586282434Sgjb	if [ -n "$name_servers_append" -o -n "$search_domains_append" ]; then
587282434Sgjb		eval "$(echo_append | parse_resolv)"
588282434Sgjb	fi
589282434Sgjb
590219732Sume	# Ensure that we only list each domain once
591219732Sume	for d in $DOMAINS; do
592219732Sume		dn="${d%%:*}"
593282434Sgjb		list_remove domain_blacklist "$dn" >/dev/null || continue
594219732Sume		case " $newdomains" in
595219732Sume		*" ${dn}:"*) continue;;
596219732Sume		esac
597219732Sume		newns=
598219732Sume		for nd in $DOMAINS; do
599219732Sume			if [ "$dn" = "${nd%%:*}" ]; then
600219732Sume				ns="${nd#*:}"
601219732Sume				while [ -n "$ns" ]; do
602219732Sume					case ",$newns," in
603219732Sume					*,${ns%%,*},*) ;;
604282434Sgjb					*) list_remove name_server_blacklist \
605282434Sgjb						"${ns%%,*}" >/dev/null \
606282434Sgjb					&& newns="$newns${newns:+,}${ns%%,*}";;
607219732Sume					esac
608219732Sume					[ "$ns" = "${ns#*,}" ] && break
609219732Sume					ns="${ns#*,}"
610219732Sume				done
611219732Sume			fi
612219732Sume		done
613282434Sgjb		if [ -n "$newns" ]; then
614282434Sgjb			newdomains="$newdomains${newdomains:+ }$dn:$newns"
615282434Sgjb		fi
616219732Sume	done
617282434Sgjb	DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
618282434Sgjb	SEARCH="$(uniqify $SEARCH)"
619282434Sgjb	SEARCH="$(list_remove domain_blacklist $SEARCH)"
620282434Sgjb	NAMESERVERS="$(uniqify $NAMESERVERS)"
621282434Sgjb	NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
622282434Sgjb	LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
623282434Sgjb	LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
624282434Sgjb	echo "DOMAIN='$DOMAIN'"
625282434Sgjb	echo "SEARCH='$SEARCH'"
626282434Sgjb	echo "NAMESERVERS='$NAMESERVERS'"
627282434Sgjb	echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
628219732Sume	echo "DOMAINS='$newdomains'"
629219732Sume}
630219732Sume
631219732Sumeforce=false
632282434SgjbVFLAG=
633304515Spfgwhile getopts a:Dd:fhIilm:pRruvVx OPT; do
634219732Sume	case "$OPT" in
635219732Sume	f) force=true;;
636219732Sume	h) usage;;
637219732Sume	m) IF_METRIC="$OPTARG";;
638219732Sume	p) IF_PRIVATE=1;;
639282434Sgjb	V)
640282434Sgjb		VFLAG=1
641282434Sgjb		if [ "$local_nameservers" = \
642282434Sgjb		    "127.* 0.0.0.0 255.255.255.255 ::1" ]
643282434Sgjb		then
644282434Sgjb			local_nameservers=
645282434Sgjb		fi
646282434Sgjb		;;
647282434Sgjb	x) IF_EXCLUSIVE=1;;
648219732Sume	'?') ;;
649219732Sume	*) cmd="$OPT"; iface="$OPTARG";;
650219732Sume	esac
651219732Sumedone
652219732Sumeshift $(($OPTIND - 1))
653282434Sgjbargs="$iface${iface:+ }$*"
654219732Sume
655219732Sume# -I inits the state dir
656219732Sumeif [ "$cmd" = I ]; then
657219732Sume	if [ -d "$VARDIR" ]; then
658219732Sume		rm -rf "$VARDIR"/*
659219732Sume	fi
660219732Sume	exit $?
661219732Sumefi
662219732Sume
663282434Sgjb# -D ensures that the listed config file base dirs exist
664282434Sgjbif [ "$cmd" = D ]; then
665282434Sgjb	config_mkdirs "$@"
666282434Sgjb	exit $?
667282434Sgjbfi
668282434Sgjb
669219732Sume# -l lists our resolv files, optionally for a specific interface
670219732Sumeif [ "$cmd" = l -o "$cmd" = i ]; then
671219732Sume	list_resolv "$cmd" "$args"
672219732Sume	exit $?
673219732Sumefi
674219732Sume
675304515Spfg# Restart a service or echo the command to restart a service
676304515Spfgif [ "$cmd" = r -o "$cmd" = R ]; then
677304515Spfg	detect_init || exit 1
678304515Spfg	if [ "$cmd" = r ]; then
679304515Spfg		set -- $args
680304515Spfg		eval $RESTARTCMD
681304515Spfg	else
682304515Spfg		echo "$RESTARTCMD"
683304515Spfg	fi
684304515Spfg	exit $?
685304515Spfgfi
686304515Spfg
687219732Sume# Not normally needed, but subscribers should be able to run independently
688282434Sgjbif [ "$cmd" = v -o -n "$VFLAG" ]; then
689219732Sume	make_vars "$iface"
690219732Sume	exit $?
691219732Sumefi
692219732Sume
693219732Sume# Test that we have valid options
694219732Sumeif [ "$cmd" = a -o "$cmd" = d ]; then
695219732Sume	if [ -z "$iface" ]; then
696219732Sume		usage "Interface not specified"
697219732Sume	fi
698219732Sumeelif [ "$cmd" != u ]; then
699219732Sume	[ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
700219732Sume	usage
701219732Sumefi
702282434Sgjb
703219732Sumeif [ "$cmd" = a ]; then
704219732Sume	for x in '/' \\ ' ' '*'; do
705219732Sume		case "$iface" in
706219732Sume		*[$x]*) error_exit "$x not allowed in interface name";;
707219732Sume		esac
708219732Sume	done
709219732Sume	for x in '.' '-' '~'; do
710219732Sume		case "$iface" in
711219732Sume		[$x]*) error_exit \
712219732Sume			"$x not allowed at start of interface name";;
713219732Sume		esac
714219732Sume	done
715219732Sume	[ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
716219732Sumefi
717219732Sume
718282434Sgjbif [ ! -d "$VARDIR" ]; then
719282434Sgjb	if [ -L "$VARDIR" ]; then
720282434Sgjb		dir="$(readlink "$VARDIR")"
721282434Sgjb		# link maybe relative
722282434Sgjb		cd "${VARDIR%/*}"
723282434Sgjb		if ! mkdir -m 0755 -p "$dir"; then
724282434Sgjb			error_exit "Failed to create needed" \
725282434Sgjb				"directory $dir"
726219732Sume		fi
727282434Sgjb	else
728282434Sgjb		if ! mkdir -m 0755 -p "$VARDIR"; then
729282434Sgjb			error_exit "Failed to create needed" \
730282434Sgjb				"directory $VARDIR"
731282434Sgjb		fi
732219732Sume	fi
733282434Sgjbfi
734282434Sgjb
735282434Sgjbif [ ! -d "$IFACEDIR" ]; then
736219732Sume	mkdir -m 0755 -p "$IFACEDIR" || \
737219732Sume		error_exit "Failed to create needed directory $IFACEDIR"
738219732Sume	if [ "$cmd" = d ]; then
739282434Sgjb		# Provide the same error messages as below
740282434Sgjb		if ! ${force}; then
741282434Sgjb			cd "$IFACEDIR"
742282434Sgjb			for i in $args; do
743282434Sgjb				warn "No resolv.conf for interface $i"
744282434Sgjb			done
745282434Sgjb		fi
746282434Sgjb		${force}
747282434Sgjb		exit $?
748219732Sume	fi
749219732Sumefi
750219732Sume
751282434Sgjb# An interface was added, changed, deleted or a general update was called.
752282434Sgjb# Due to exclusivity we need to ensure that this is an atomic operation.
753282434Sgjb# Our subscribers *may* need this as well if the init system is sub par.
754282434Sgjb# As such we spinlock at this point as best we can.
755282434Sgjb# We don't use flock(1) because it's not widely available and normally resides
756282434Sgjb# in /usr which we do our very best to operate without.
757282434Sgjb[ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
758282434Sgjb: ${lock_timeout:=10}
759282434Sgjbwhile true; do
760282434Sgjb	if mkdir "$LOCKDIR" 2>/dev/null; then
761282434Sgjb		trap 'rm -rf "$LOCKDIR";' EXIT
762282434Sgjb		trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
763282434Sgjb		echo $$ >"$LOCKDIR/pid"
764282434Sgjb		break
765282434Sgjb	fi
766282434Sgjb	pid=$(cat "$LOCKDIR/pid")
767282434Sgjb	if ! kill -0 "$pid"; then
768282434Sgjb		warn "clearing stale lock pid $pid"
769282434Sgjb		rm -rf "$LOCKDIR"
770282434Sgjb		continue
771282434Sgjb	fi
772282434Sgjb	lock_timeout=$(($lock_timeout - 1))
773282434Sgjb	if [ "$lock_timeout" -le 0 ]; then
774282434Sgjb		error_exit "timed out waiting for lock from pid $pid"
775282434Sgjb	fi
776282434Sgjb	sleep 1
777282434Sgjbdone
778282434Sgjb
779282434Sgjbcase "$cmd" in
780282434Sgjba)
781219732Sume	# Read resolv.conf from stdin
782225524Shrs	resolv="$(cat)"
783282434Sgjb	changed=false
784282434Sgjb	changedfile=false
785219732Sume	# If what we are given matches what we have, then do nothing
786219732Sume	if [ -e "$IFACEDIR/$iface" ]; then
787282434Sgjb		if [ "$(echo "$resolv")" != \
788219732Sume			"$(cat "$IFACEDIR/$iface")" ]
789219732Sume		then
790282434Sgjb			changed=true
791282434Sgjb			changedfile=true
792219732Sume		fi
793282434Sgjb	else
794282434Sgjb		changed=true
795282434Sgjb		changedfile=true
796219732Sume	fi
797282434Sgjb
798282434Sgjb	# Set metric and private before creating the interface resolv.conf file
799282434Sgjb	# to ensure that it will have the correct flags
800219732Sume	[ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
801282434Sgjb	oldmetric="$METRICDIR/"*" $iface"
802282434Sgjb	newmetric=
803219732Sume	if [ -n "$IF_METRIC" ]; then
804219732Sume		# Pad metric to 6 characters, so 5 is less than 10
805219732Sume		while [ ${#IF_METRIC} -le 6 ]; do
806219732Sume			IF_METRIC="0$IF_METRIC"
807219732Sume		done
808282434Sgjb		newmetric="$METRICDIR/$IF_METRIC $iface"
809219732Sume	fi
810282434Sgjb	rm -f "$METRICDIR/"*" $iface"
811282434Sgjb	[ "$oldmetric" != "$newmetric" -a \
812282434Sgjb	    "$oldmetric" != "$METRICDIR/* $iface" ] &&
813282434Sgjb		changed=true
814282434Sgjb	[ -n "$newmetric" ] && echo " " >"$newmetric"
815282434Sgjb
816219732Sume	case "$IF_PRIVATE" in
817219732Sume	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
818219732Sume		if [ ! -d "$PRIVATEDIR" ]; then
819219732Sume			[ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
820219732Sume			mkdir "$PRIVATEDIR"
821219732Sume		fi
822282434Sgjb		[ -e "$PRIVATEDIR/$iface" ] || changed=true
823219732Sume		[ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
824219732Sume		;;
825219732Sume	*)
826219732Sume		if [ -e "$PRIVATEDIR/$iface" ]; then
827219732Sume			rm -f "$PRIVATEDIR/$iface"
828282434Sgjb			changed=true
829219732Sume		fi
830219732Sume		;;
831219732Sume	esac
832219732Sume
833282434Sgjb	oldexcl=
834282434Sgjb	for x in "$EXCLUSIVEDIR/"*" $iface"; do
835282434Sgjb		if [ -f "$x" ]; then
836282434Sgjb			oldexcl="$x"
837282434Sgjb			break
838282434Sgjb		fi
839282434Sgjb	done
840282434Sgjb	case "$IF_EXCLUSIVE" in
841282434Sgjb	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
842282434Sgjb		if [ ! -d "$EXCLUSIVEDIR" ]; then
843282434Sgjb			[ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
844282434Sgjb			mkdir "$EXCLUSIVEDIR"
845282434Sgjb		fi
846282434Sgjb		cd "$EXCLUSIVEDIR"
847282434Sgjb		for x in *; do
848282434Sgjb			[ -f "$x" ] && break
849282434Sgjb		done
850282434Sgjb		if [ "${x#* }" != "$iface" ]; then
851282434Sgjb			if [ "$x" = "${x% *}" ]; then
852282434Sgjb				x=10000000
853282434Sgjb			else
854282434Sgjb				x="${x% *}"
855282434Sgjb			fi
856282434Sgjb			if [ "$x" = "0000000" ]; then
857282434Sgjb				warn "exclusive underflow"
858282434Sgjb			else
859282434Sgjb				x=$(($x - 1))
860282434Sgjb			fi
861282434Sgjb			if [ -d "$EXCLUSIVEDIR" ]; then
862282434Sgjb				echo " " >"$EXCLUSIVEDIR/$x $iface"
863282434Sgjb			fi
864282434Sgjb			changed=true
865282434Sgjb		fi
866282434Sgjb		;;
867282434Sgjb	*)
868282434Sgjb		if [ -f "$oldexcl" ]; then
869282434Sgjb			rm -f "$oldexcl"
870282434Sgjb			changed=true
871282434Sgjb		fi
872282434Sgjb		;;
873282434Sgjb	esac
874282434Sgjb
875282434Sgjb	if $changedfile; then
876282434Sgjb		printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
877282434Sgjb	elif ! $changed; then
878282434Sgjb		exit 0
879282434Sgjb	fi
880282434Sgjb	unset changed changedfile oldmetric newmetric x oldexcl
881282434Sgjb	;;
882282434Sgjb
883282434Sgjbd)
884282434Sgjb	# Delete any existing information about the interface
885282434Sgjb	cd "$IFACEDIR"
886282434Sgjb	changed=false
887282434Sgjb	for i in $args; do
888282434Sgjb		if [ -e "$i" ]; then
889282434Sgjb			changed=true
890282434Sgjb		elif ! ${force}; then
891282434Sgjb			warn "No resolv.conf for interface $i"
892282434Sgjb		fi
893282434Sgjb		rm -f "$i" "$METRICDIR/"*" $i" \
894282434Sgjb			"$PRIVATEDIR/$i" \
895282434Sgjb			"$EXCLUSIVEDIR/"*" $i" || exit $?
896282434Sgjb	done
897282434Sgjb	if ! ${changed}; then
898282434Sgjb		# Set the return code based on the forced flag
899282434Sgjb		${force}
900282434Sgjb		exit $?
901282434Sgjb	fi
902282434Sgjb	unset changed i
903282434Sgjb	;;
904282434Sgjbesac
905282434Sgjb
906282434Sgjbcase "${resolvconf:-YES}" in
907282434Sgjb[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
908282434Sgjb*) exit 0;;
909282434Sgjbesac
910282434Sgjb
911304515Spfg# Try and detect a suitable init system for our scripts
912304515Spfgdetect_init
913304515Spfgexport RESTARTCMD RCDIR _NOINIT_WARNED
914304515Spfg
915219732Sumeeval "$(make_vars)"
916219732Sumeexport RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
917219732Sume: ${list_resolv:=list_resolv -l}
918219732Sumeretval=0
919296190Spfg
920296190Spfg# Run scripts in the same directory resolvconf is run from
921304515Spfg# in case any scripts accidentally dump files in the wrong place.
922296190Spfgcd "$_PWD"
923219732Sumefor script in "$LIBEXECDIR"/*; do
924219732Sume	if [ -f "$script" ]; then
925282434Sgjb		eval script_enabled="\$${script##*/}"
926282434Sgjb		case "${script_enabled:-YES}" in
927282434Sgjb		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
928282434Sgjb		*) continue;;
929282434Sgjb		esac
930219732Sume		if [ -x "$script" ]; then
931219732Sume			"$script" "$cmd" "$iface"
932219732Sume		else
933282434Sgjb			(set -- "$cmd" "$iface"; . "$script")
934219732Sume		fi
935219732Sume		retval=$(($retval + $?))
936219732Sume	fi
937219732Sumedone
938219732Sumeexit $retval
939