1#!/bin/sh
2# Copyright (c) 2007-2009 Roy Marples
3# All rights reserved
4
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions
7# are met:
8#     * Redistributions of source code must retain the above copyright
9#       notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11#       copyright notice, this list of conditions and the following
12#       disclaimer in the documentation and/or other materials provided
13#       with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27RESOLVCONF="$0"
28SYSCONFDIR=@SYSCONFDIR@
29LIBEXECDIR=@LIBEXECDIR@
30VARDIR=@VARDIR@
31# Support original resolvconf configuration layout
32# as well as the openresolv config file
33if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
34	. "$SYSCONFDIR"/resolvconf.conf
35	[ -n "$state_dir" ] && VARDIR="$state_dir"
36elif [ -d "$SYSCONFDIR/resolvconf" ]; then
37	SYSCONFDIR="$SYSCONFDIR/resolvconf"
38	if [ -f "$SYSCONFDIR"/interface-order ]; then
39		interface_order="$(cat "$SYSCONFDIR"/interface-order)"
40	fi
41fi
42IFACEDIR="$VARDIR/interfaces"
43METRICDIR="$VARDIR/metrics"
44PRIVATEDIR="$VARDIR/private"
45
46: ${dynamic_order:=tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*}
47: ${interface_order:=lo lo[0-9]*}
48
49error_exit()
50{
51	echo "$*" >&2
52	exit 1
53}
54
55usage()
56{
57	cat <<-EOF
58	Usage: ${RESOLVCONF##*/} [options]
59
60	Inform the system about any DNS updates.
61
62	Options:
63	  -a \$INTERFACE    Add DNS information to the specified interface
64	                   (DNS supplied via stdin in resolv.conf format)
65	  -m metric        Give the added DNS information a metric
66	  -p               Mark the interface as private
67	  -d \$INTERFACE    Delete DNS information from the specified interface
68	  -f               Ignore non existant interfaces
69	  -I               Init the state dir
70	  -u               Run updates from our current DNS information
71	  -l [\$PATTERN]    Show DNS information, optionally from interfaces
72	                   that match the specified pattern
73	  -i [\$PATTERN]    Show interfaces that have supplied DNS information
74                   optionally from interfaces that match the specified
75                   pattern
76	  -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
77	  		   the console
78	  -h               Show this help cruft
79	EOF
80	[ -z "$1" ] && exit 0
81	echo
82	error_exit "$*"
83}
84
85echo_resolv()
86{
87	local line=
88	[ -n "$1" -a -e "$IFACEDIR/$1" ] || return 1
89	echo "# resolv.conf from $1"
90	# Our variable maker works of the fact each resolv.conf per interface
91	# is separated by blank lines.
92	# So we remove them when echoing them.
93	while read line; do
94		[ -n "$line" ] && echo "$line"
95	done < "$IFACEDIR/$1"
96	echo
97}
98
99# Parse resolv.conf's and make variables
100# for domain name servers, search name servers and global nameservers
101parse_resolv()
102{
103	local line= ns= ds= search= d= n= newns=
104	local new=true iface= private=false p=
105
106	echo "DOMAINS="
107	echo "SEARCH="
108	echo "NAMESERVERS="
109
110	while read line; do
111		case "$line" in
112		"# resolv.conf from "*)
113			if ${new}; then
114				iface="${line#\# resolv.conf from *}"
115				new=false
116				if [ -e "$PRIVATEDIR/$iface" ]; then
117					private=true
118				else
119					# Allow expansion
120					cd "$IFACEDIR"
121					private=false
122					for p in $private_interfaces; do
123						if [ "$p" = "$iface" ]; then
124							private=true
125							break
126						fi
127					done
128				fi
129			fi
130			;;
131		"nameserver "*)
132			case "${line#* }" in
133			127.*|0.0.0.0|255.255.255.255) continue;;
134			esac
135			ns="$ns${line#* } "
136			;;
137		"domain "*|"search "*)
138			search="${line#* }"
139			;;
140		*)
141			[ -n "$line" ] && continue
142			if [ -n "$ns" -a -n "$search" ]; then
143				newns=
144				for n in $ns; do
145					newns="$newns${newns:+,}$n"
146				done
147				ds=
148				for d in $search; do
149					ds="$ds${ds:+ }$d:$newns"
150				done
151				echo "DOMAINS=\"\$DOMAINS $ds\""
152			fi
153			echo "SEARCH=\"\$SEARCH $search\""
154			if ! $private; then
155				echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
156			fi
157			ns=
158			search=
159			new=true
160			;;
161		esac
162	done
163}
164
165uniqify()
166{
167	local result=
168	while [ -n "$1" ]; do
169		case " $result " in
170		*" $1 "*);;
171		*) result="$result $1";;
172		esac
173		shift
174	done
175	echo "${result# *}"
176}
177
178list_resolv()
179{
180	[ -d "$IFACEDIR" ] || return 0
181
182	local report=false list= retval=0 cmd="$1"
183	shift
184
185	# If we have an interface ordering list, then use that.
186	# It works by just using pathname expansion in the interface directory.
187	if [ -n "$1" ]; then
188		list="$@"
189		$force || report=true
190	else
191		cd "$IFACEDIR"
192		for i in $interface_order; do
193			[ -e "$i" ] && list="$list $i"
194		done
195		for i in $dynamic_order; do
196			if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
197				list="$list $i"
198			fi
199		done
200		if [ -d "$METRICDIR" ]; then
201			cd "$METRICDIR"
202			for i in *; do
203				list="$list ${i#* }"
204			done
205		fi
206		list="$list *"
207	fi
208
209	cd "$IFACEDIR"
210	for i in $(uniqify $list); do
211		# Only list interfaces which we really have
212		if ! [ -e "$i" ]; then
213			if $report; then
214				echo "No resolv.conf for interface $i" >&2
215				retval=$(($retval + 1))
216			fi
217			continue
218		fi
219
220		if [ "$cmd" = i -o "$cmd" = "-i" ]; then
221			printf "$i "
222		else
223			echo_resolv "$i"
224		fi
225	done
226	[ "$cmd" = i -o "$cmd" = "-i" ] && echo
227	return $retval
228}
229
230make_vars()
231{
232	eval "$(list_resolv -l "$@" | parse_resolv)"
233
234	# Ensure that we only list each domain once
235	newdomains=
236	for d in $DOMAINS; do
237		dn="${d%%:*}"
238		case " $newdomains" in
239		*" ${dn}:"*) continue;;
240		esac
241		newdomains="$newdomains${newdomains:+ }$dn:"
242		newns=
243		for nd in $DOMAINS; do
244			if [ "$dn" = "${nd%%:*}" ]; then
245				ns="${nd#*:}"
246				while [ -n "$ns" ]; do
247					case ",$newns," in
248					*,${ns%%,*},*) ;;
249					*) newns="$newns${newns:+,}${ns%%,*}";;
250					esac
251					[ "$ns" = "${ns#*,}" ] && break
252					ns="${ns#*,}"
253				done
254			fi
255		done
256		newdomains="$newdomains$newns"
257	done
258	echo "DOMAINS='$newdomains'"
259	echo "SEARCH='$(uniqify $SEARCH)'"
260	echo "NAMESERVERS='$(uniqify $NAMESERVERS)'"
261}
262
263force=false
264while getopts a:d:fhilm:puv OPT; do
265	case "$OPT" in
266	f) force=true;;
267	h) usage;;
268	m) IF_METRIC="$OPTARG";;
269	p) IF_PRIVATE=1;;
270	'?') ;;
271	*) cmd="$OPT"; iface="$OPTARG";;
272	esac
273done
274shift $(($OPTIND - 1))
275args="$iface${iface:+ }$@"
276
277# -I inits the state dir
278if [ "$cmd" = I ]; then
279	if [ -d "$VARDIR" ]; then
280		rm -rf "$VARDIR"/*
281	fi
282	exit $?
283fi
284
285# -l lists our resolv files, optionally for a specific interface
286if [ "$cmd" = l -o "$cmd" = i ]; then
287	list_resolv "$cmd" "$args"
288	exit $?
289fi
290
291# Not normally needed, but subscribers should be able to run independently
292if [ "$cmd" = v ]; then
293	make_vars "$iface"
294	exit $?
295fi
296
297# Test that we have valid options
298if [ "$cmd" = a -o "$cmd" = d ]; then
299	if [ -z "$iface" ]; then
300		usage "Interface not specified"
301	fi
302elif [ "$cmd" != u ]; then
303	[ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
304	usage
305fi
306if [ "$cmd" = a ]; then
307	for x in '/' \\ ' ' '*'; do
308		case "$iface" in
309		*[$x]*) error_exit "$x not allowed in interface name";;
310		esac
311	done
312	for x in '.' '-' '~'; do
313		case "$iface" in
314		[$x]*) error_exit \
315			"$x not allowed at start of interface name";;
316		esac
317	done
318	[ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
319fi
320
321if [ ! -d "$IFACEDIR" ]; then
322	if [ ! -d "$VARDIR" ]; then
323		if [ -L "$VARDIR" ]; then
324			dir="$(readlink "$VARDIR")"
325			# link maybe relative
326			cd "${VARDIR%/*}"
327			if ! mkdir -m 0755 -p "$dir"; then
328				error_exit "Failed to create needed" \
329					"directory $dir"
330			fi
331		else
332			if ! mkdir -m 0755 -p "$VARDIR"; then
333				error_exit "Failed to create needed" \
334					"directory $VARDIR"
335			fi
336		fi
337	fi
338	mkdir -m 0755 -p "$IFACEDIR" || \
339		error_exit "Failed to create needed directory $IFACEDIR"
340else
341	# Delete any existing information about the interface
342	if [ "$cmd" = d ]; then
343		cd "$IFACEDIR"
344		for i in $args; do
345			if [ "$cmd" = d -a ! -e "$i" ]; then
346				$force && continue
347				error_exit "No resolv.conf for" \
348					"interface $i"
349			fi
350			rm -f "$i" "$METRICDIR/"*" $i" \
351				"$PRIVATEDIR/$i" || exit $?
352		done
353	fi
354fi
355
356if [ "$cmd" = a ]; then
357	# Read resolv.conf from stdin
358	resolv="$(cat)\n"
359	# If what we are given matches what we have, then do nothing
360	if [ -e "$IFACEDIR/$iface" ]; then
361		if [ "$(printf "$resolv")" = \
362			"$(cat "$IFACEDIR/$iface")" ]
363		then
364			exit 0
365		fi
366		rm "$IFACEDIR/$iface"
367	fi
368	printf "$resolv" >"$IFACEDIR/$iface" || exit $?
369	[ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
370	rm -f "$METRICDIR/"*" $iface"
371	if [ -n "$IF_METRIC" ]; then
372		# Pad metric to 6 characters, so 5 is less than 10
373		while [ ${#IF_METRIC} -le 6 ]; do
374			IF_METRIC="0$IF_METRIC"
375		done
376		echo " " >"$METRICDIR/$IF_METRIC $iface"
377	fi
378	case "$IF_PRIVATE" in
379	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
380		if [ ! -d "$PRIVATEDIR" ]; then
381			[ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
382			mkdir "$PRIVATEDIR"
383		fi
384		[ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
385		;;
386	*)
387		if [ -e "$PRIVATEDIR/$iface" ]; then
388			rm -f "$PRIVATEDIR/$iface"
389		fi
390		;;
391	esac
392fi
393
394eval "$(make_vars)"
395export RESOLVCONF DOMAINS SEARCH NAMESERVERS
396: ${list_resolv:=list_resolv -l}
397retval=0
398for script in "$LIBEXECDIR"/*; do
399	if [ -f "$script" ]; then
400		if [ -x "$script" ]; then
401			"$script" "$cmd" "$iface"
402		else
403			(. "$script" "$cmd" "$fiace")
404		fi
405		retval=$(($retval + $?))
406	fi
407done
408exit $retval
409