1219732Sume#!/bin/sh
2219732Sume# Copyright (c) 2007-2009 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"
28219732SumeSYSCONFDIR=@SYSCONFDIR@
29219732SumeLIBEXECDIR=@LIBEXECDIR@
30219732SumeVARDIR=@VARDIR@
31219732Sume# Support original resolvconf configuration layout
32219732Sume# as well as the openresolv config file
33219732Sumeif [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
34219732Sume	. "$SYSCONFDIR"/resolvconf.conf
35219732Sume	[ -n "$state_dir" ] && VARDIR="$state_dir"
36219732Sumeelif [ -d "$SYSCONFDIR/resolvconf" ]; then
37219732Sume	SYSCONFDIR="$SYSCONFDIR/resolvconf"
38219732Sume	if [ -f "$SYSCONFDIR"/interface-order ]; then
39219732Sume		interface_order="$(cat "$SYSCONFDIR"/interface-order)"
40219732Sume	fi
41219732Sumefi
42219732SumeIFACEDIR="$VARDIR/interfaces"
43219732SumeMETRICDIR="$VARDIR/metrics"
44219732SumePRIVATEDIR="$VARDIR/private"
45219732Sume
46219732Sume: ${dynamic_order:=tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*}
47219732Sume: ${interface_order:=lo lo[0-9]*}
48219732Sume
49219732Sumeerror_exit()
50219732Sume{
51219732Sume	echo "$*" >&2
52219732Sume	exit 1
53219732Sume}
54219732Sume
55219732Sumeusage()
56219732Sume{
57219732Sume	cat <<-EOF
58219732Sume	Usage: ${RESOLVCONF##*/} [options]
59219732Sume
60219732Sume	Inform the system about any DNS updates.
61219732Sume
62219732Sume	Options:
63219732Sume	  -a \$INTERFACE    Add DNS information to the specified interface
64219732Sume	                   (DNS supplied via stdin in resolv.conf format)
65219732Sume	  -m metric        Give the added DNS information a metric
66219732Sume	  -p               Mark the interface as private
67219732Sume	  -d \$INTERFACE    Delete DNS information from the specified interface
68219732Sume	  -f               Ignore non existant interfaces
69219732Sume	  -I               Init the state dir
70219732Sume	  -u               Run updates from our current DNS information
71219732Sume	  -l [\$PATTERN]    Show DNS information, optionally from interfaces
72219732Sume	                   that match the specified pattern
73219732Sume	  -i [\$PATTERN]    Show interfaces that have supplied DNS information
74219732Sume                   optionally from interfaces that match the specified
75219732Sume                   pattern
76219732Sume	  -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
77219732Sume	  		   the console
78219732Sume	  -h               Show this help cruft
79219732Sume	EOF
80219732Sume	[ -z "$1" ] && exit 0
81219732Sume	echo
82219732Sume	error_exit "$*"
83219732Sume}
84219732Sume
85219732Sumeecho_resolv()
86219732Sume{
87219732Sume	local line=
88219732Sume	[ -n "$1" -a -e "$IFACEDIR/$1" ] || return 1
89219732Sume	echo "# resolv.conf from $1"
90219732Sume	# Our variable maker works of the fact each resolv.conf per interface
91219732Sume	# is separated by blank lines.
92219732Sume	# So we remove them when echoing them.
93219732Sume	while read line; do
94219732Sume		[ -n "$line" ] && echo "$line"
95219732Sume	done < "$IFACEDIR/$1"
96219732Sume	echo
97219732Sume}
98219732Sume
99219732Sume# Parse resolv.conf's and make variables
100219732Sume# for domain name servers, search name servers and global nameservers
101219732Sumeparse_resolv()
102219732Sume{
103219732Sume	local line= ns= ds= search= d= n= newns=
104219732Sume	local new=true iface= private=false p=
105219732Sume
106219732Sume	echo "DOMAINS="
107219732Sume	echo "SEARCH=\"$search_domains\""
108219732Sume	# let our subscribers know about global nameservers
109219732Sume	for n in $name_servers; do
110219732Sume		case "$n" in
111219732Sume		127.*|0.0.0.0|255.255.255.255|::1) :;;
112219732Sume		*) newns="$newns${newns:+ }$n";;
113219732Sume		esac
114219732Sume	done
115219732Sume	echo "NAMESERVERS=\"$newns\""
116219732Sume	echo "LOCALNAMESERVERS="
117219732Sume	newns=
118219732Sume
119219732Sume	while read line; do
120219732Sume		case "$line" in
121219732Sume		"# resolv.conf from "*)
122219732Sume			if ${new}; then
123219732Sume				iface="${line#\# resolv.conf from *}"
124219732Sume				new=false
125219732Sume				if [ -e "$PRIVATEDIR/$iface" ]; then
126219732Sume					private=true
127219732Sume				else
128219732Sume					# Allow expansion
129219732Sume					cd "$IFACEDIR"
130219732Sume					private=false
131219732Sume					for p in $private_interfaces; do
132219732Sume						if [ "$p" = "$iface" ]; then
133219732Sume							private=true
134219732Sume							break
135219732Sume						fi
136219732Sume					done
137219732Sume				fi
138219732Sume			fi
139219732Sume			;;
140219732Sume		"nameserver "*)
141219732Sume			case "${line#* }" in
142219732Sume			127.*|0.0.0.0|255.255.255.255|::1)
143219732Sume				echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\""
144219732Sume				continue
145219732Sume				;;
146219732Sume			esac
147219732Sume			ns="$ns${line#* } "
148219732Sume			;;
149219732Sume		"domain "*|"search "*)
150219732Sume			search="${line#* }"
151219732Sume			;;
152219732Sume		*)
153219732Sume			[ -n "$line" ] && continue
154219732Sume			if [ -n "$ns" -a -n "$search" ]; then
155219732Sume				newns=
156219732Sume				for n in $ns; do
157219732Sume					newns="$newns${newns:+,}$n"
158219732Sume				done
159219732Sume				ds=
160219732Sume				for d in $search; do
161219732Sume					ds="$ds${ds:+ }$d:$newns"
162219732Sume				done
163219732Sume				echo "DOMAINS=\"\$DOMAINS $ds\""
164219732Sume			fi
165219732Sume			echo "SEARCH=\"\$SEARCH $search\""
166219732Sume			if ! $private; then
167219732Sume				echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
168219732Sume			fi
169219732Sume			ns=
170219732Sume			search=
171219732Sume			new=true
172219732Sume			;;
173219732Sume		esac
174219732Sume	done
175219732Sume}
176219732Sume
177219732Sumeuniqify()
178219732Sume{
179219732Sume	local result=
180219732Sume	while [ -n "$1" ]; do
181219732Sume		case " $result " in
182219732Sume		*" $1 "*);;
183219732Sume		*) result="$result $1";;
184219732Sume		esac
185219732Sume		shift
186219732Sume	done
187219732Sume	echo "${result# *}"
188219732Sume}
189219732Sume
190219732Sumelist_resolv()
191219732Sume{
192219732Sume	[ -d "$IFACEDIR" ] || return 0
193219732Sume
194219732Sume	local report=false list= retval=0 cmd="$1"
195219732Sume	shift
196219732Sume
197219732Sume	# If we have an interface ordering list, then use that.
198219732Sume	# It works by just using pathname expansion in the interface directory.
199219732Sume	if [ -n "$1" ]; then
200219732Sume		list="$@"
201219732Sume		$force || report=true
202219732Sume	else
203219732Sume		cd "$IFACEDIR"
204219732Sume		for i in $interface_order; do
205219732Sume			[ -e "$i" ] && list="$list $i"
206219732Sume		done
207219732Sume		for i in $dynamic_order; do
208219732Sume			if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
209219732Sume				list="$list $i"
210219732Sume			fi
211219732Sume		done
212219732Sume		if [ -d "$METRICDIR" ]; then
213219732Sume			cd "$METRICDIR"
214219732Sume			for i in *; do
215219732Sume				list="$list ${i#* }"
216219732Sume			done
217219732Sume		fi
218219732Sume		list="$list *"
219219732Sume	fi
220219732Sume
221219732Sume	cd "$IFACEDIR"
222219732Sume	for i in $(uniqify $list); do
223219732Sume		# Only list interfaces which we really have
224219732Sume		if ! [ -e "$i" ]; then
225219732Sume			if $report; then
226219732Sume				echo "No resolv.conf for interface $i" >&2
227219732Sume				retval=$(($retval + 1))
228219732Sume			fi
229219732Sume			continue
230219732Sume		fi
231219732Sume		
232219732Sume		if [ "$cmd" = i -o "$cmd" = "-i" ]; then
233219732Sume			printf "$i "
234219732Sume		else
235219732Sume			echo_resolv "$i"
236219732Sume		fi
237219732Sume	done
238219732Sume	[ "$cmd" = i -o "$cmd" = "-i" ] && echo
239219732Sume	return $retval
240219732Sume}
241219732Sume
242219732Sumemake_vars()
243219732Sume{
244219732Sume	eval "$(list_resolv -l "$@" | parse_resolv)"
245219732Sume
246219732Sume	# Ensure that we only list each domain once
247219732Sume	newdomains=
248219732Sume	for d in $DOMAINS; do
249219732Sume		dn="${d%%:*}"
250219732Sume		case " $newdomains" in
251219732Sume		*" ${dn}:"*) continue;;
252219732Sume		esac
253219732Sume		newdomains="$newdomains${newdomains:+ }$dn:"
254219732Sume		newns=
255219732Sume		for nd in $DOMAINS; do
256219732Sume			if [ "$dn" = "${nd%%:*}" ]; then
257219732Sume				ns="${nd#*:}"
258219732Sume				while [ -n "$ns" ]; do
259219732Sume					case ",$newns," in
260219732Sume					*,${ns%%,*},*) ;;
261219732Sume					*) newns="$newns${newns:+,}${ns%%,*}";;
262219732Sume					esac
263219732Sume					[ "$ns" = "${ns#*,}" ] && break
264219732Sume					ns="${ns#*,}"
265219732Sume				done
266219732Sume			fi
267219732Sume		done
268219732Sume		newdomains="$newdomains$newns"
269219732Sume	done
270219732Sume	echo "DOMAINS='$newdomains'"
271219732Sume	echo "SEARCH='$(uniqify $SEARCH)'"
272219732Sume	echo "NAMESERVERS='$(uniqify $NAMESERVERS)'"
273219732Sume	echo "LOCALNAMESERVERS='$(uniqify $LOCALNAMESERVERS)'"
274219732Sume}
275219732Sume
276219732Sumeforce=false
277219732Sumewhile getopts a:d:fhIilm:puv OPT; do
278219732Sume	case "$OPT" in
279219732Sume	f) force=true;;
280219732Sume	h) usage;;
281219732Sume	m) IF_METRIC="$OPTARG";;
282219732Sume	p) IF_PRIVATE=1;;
283219732Sume	'?') ;;
284219732Sume	*) cmd="$OPT"; iface="$OPTARG";;
285219732Sume	esac
286219732Sumedone
287219732Sumeshift $(($OPTIND - 1))
288219732Sumeargs="$iface${iface:+ }$@"
289219732Sume
290219732Sume# -I inits the state dir
291219732Sumeif [ "$cmd" = I ]; then
292219732Sume	if [ -d "$VARDIR" ]; then
293219732Sume		rm -rf "$VARDIR"/*
294219732Sume	fi
295219732Sume	exit $?
296219732Sumefi
297219732Sume
298219732Sume# -l lists our resolv files, optionally for a specific interface
299219732Sumeif [ "$cmd" = l -o "$cmd" = i ]; then
300219732Sume	list_resolv "$cmd" "$args"
301219732Sume	exit $?
302219732Sumefi
303219732Sume
304219732Sume# Not normally needed, but subscribers should be able to run independently
305219732Sumeif [ "$cmd" = v ]; then
306219732Sume	make_vars "$iface"
307219732Sume	exit $?
308219732Sumefi
309219732Sume
310219732Sume# Test that we have valid options
311219732Sumeif [ "$cmd" = a -o "$cmd" = d ]; then
312219732Sume	if [ -z "$iface" ]; then
313219732Sume		usage "Interface not specified"
314219732Sume	fi
315219732Sumeelif [ "$cmd" != u ]; then
316219732Sume	[ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
317219732Sume	usage
318219732Sumefi
319219732Sumeif [ "$cmd" = a ]; then
320219732Sume	for x in '/' \\ ' ' '*'; do
321219732Sume		case "$iface" in
322219732Sume		*[$x]*) error_exit "$x not allowed in interface name";;
323219732Sume		esac
324219732Sume	done
325219732Sume	for x in '.' '-' '~'; do
326219732Sume		case "$iface" in
327219732Sume		[$x]*) error_exit \
328219732Sume			"$x not allowed at start of interface name";;
329219732Sume		esac
330219732Sume	done
331219732Sume	[ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
332219732Sumefi
333219732Sume
334219732Sumeif [ ! -d "$IFACEDIR" ]; then
335219732Sume	if [ ! -d "$VARDIR" ]; then
336219732Sume		if [ -L "$VARDIR" ]; then
337219732Sume			dir="$(readlink "$VARDIR")"
338219732Sume			# link maybe relative
339219732Sume			cd "${VARDIR%/*}"
340219732Sume			if ! mkdir -m 0755 -p "$dir"; then
341219732Sume				error_exit "Failed to create needed" \
342219732Sume					"directory $dir"
343219732Sume			fi
344219732Sume		else
345219732Sume			if ! mkdir -m 0755 -p "$VARDIR"; then
346219732Sume				error_exit "Failed to create needed" \
347219732Sume					"directory $VARDIR"
348219732Sume			fi
349219732Sume		fi
350219732Sume	fi
351219732Sume	mkdir -m 0755 -p "$IFACEDIR" || \
352219732Sume		error_exit "Failed to create needed directory $IFACEDIR"
353219732Sumeelse
354219732Sume	# Delete any existing information about the interface
355219732Sume	if [ "$cmd" = d ]; then
356219732Sume		cd "$IFACEDIR"
357219732Sume		for i in $args; do
358219732Sume			if [ "$cmd" = d -a ! -e "$i" ]; then
359219732Sume				$force && continue
360219732Sume				error_exit "No resolv.conf for" \
361219732Sume					"interface $i"
362219732Sume			fi
363219732Sume			rm -f "$i" "$METRICDIR/"*" $i" \
364219732Sume				"$PRIVATEDIR/$i" || exit $?
365219732Sume		done
366219732Sume	fi
367219732Sumefi
368219732Sume
369219732Sumeif [ "$cmd" = a ]; then
370219732Sume	# Read resolv.conf from stdin
371225524Shrs	resolv="$(cat)"
372219732Sume	# If what we are given matches what we have, then do nothing
373219732Sume	if [ -e "$IFACEDIR/$iface" ]; then
374225524Shrs		if [ "$(echo "$resolv")" = \
375219732Sume			"$(cat "$IFACEDIR/$iface")" ]
376219732Sume		then
377219732Sume			exit 0
378219732Sume		fi
379219732Sume		rm "$IFACEDIR/$iface"
380219732Sume	fi
381225524Shrs	echo "$resolv" >"$IFACEDIR/$iface" || exit $?
382219732Sume	[ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
383219732Sume	rm -f "$METRICDIR/"*" $iface"
384219732Sume	if [ -n "$IF_METRIC" ]; then
385219732Sume		# Pad metric to 6 characters, so 5 is less than 10
386219732Sume		while [ ${#IF_METRIC} -le 6 ]; do
387219732Sume			IF_METRIC="0$IF_METRIC"
388219732Sume		done
389219732Sume		echo " " >"$METRICDIR/$IF_METRIC $iface"
390219732Sume	fi
391219732Sume	case "$IF_PRIVATE" in
392219732Sume	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
393219732Sume		if [ ! -d "$PRIVATEDIR" ]; then
394219732Sume			[ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
395219732Sume			mkdir "$PRIVATEDIR"
396219732Sume		fi
397219732Sume		[ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
398219732Sume		;;
399219732Sume	*)
400219732Sume		if [ -e "$PRIVATEDIR/$iface" ]; then
401219732Sume			rm -f "$PRIVATEDIR/$iface"
402219732Sume		fi
403219732Sume		;;
404219732Sume	esac
405219732Sumefi
406219732Sume
407219732Sumeeval "$(make_vars)"
408219732Sumeexport RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
409219732Sume: ${list_resolv:=list_resolv -l}
410219732Sumeretval=0
411219732Sumefor script in "$LIBEXECDIR"/*; do
412219732Sume	if [ -f "$script" ]; then
413219732Sume		if [ -x "$script" ]; then
414219732Sume			"$script" "$cmd" "$iface"
415219732Sume		else
416219732Sume			(. "$script" "$cmd" "$iface")
417219732Sume		fi
418219732Sume		retval=$(($retval + $?))
419219732Sume	fi
420219732Sumedone
421219732Sumeexit $retval
422