xref: /freebsd/contrib/openresolv/resolvconf.in (revision 2b15cb3d0922bd70ea592f0da9b4a5b167f4d53f)
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=\"$search_domains\""
108	# let our subscribers know about global nameservers
109	for n in $name_servers; do
110		case "$n" in
111		127.*|0.0.0.0|255.255.255.255|::1) :;;
112		*) newns="$newns${newns:+ }$n";;
113		esac
114	done
115	echo "NAMESERVERS=\"$newns\""
116	echo "LOCALNAMESERVERS="
117	newns=
118
119	while read line; do
120		case "$line" in
121		"# resolv.conf from "*)
122			if ${new}; then
123				iface="${line#\# resolv.conf from *}"
124				new=false
125				if [ -e "$PRIVATEDIR/$iface" ]; then
126					private=true
127				else
128					# Allow expansion
129					cd "$IFACEDIR"
130					private=false
131					for p in $private_interfaces; do
132						if [ "$p" = "$iface" ]; then
133							private=true
134							break
135						fi
136					done
137				fi
138			fi
139			;;
140		"nameserver "*)
141			case "${line#* }" in
142			127.*|0.0.0.0|255.255.255.255|::1)
143				echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\""
144				continue
145				;;
146			esac
147			ns="$ns${line#* } "
148			;;
149		"domain "*|"search "*)
150			search="${line#* }"
151			;;
152		*)
153			[ -n "$line" ] && continue
154			if [ -n "$ns" -a -n "$search" ]; then
155				newns=
156				for n in $ns; do
157					newns="$newns${newns:+,}$n"
158				done
159				ds=
160				for d in $search; do
161					ds="$ds${ds:+ }$d:$newns"
162				done
163				echo "DOMAINS=\"\$DOMAINS $ds\""
164			fi
165			echo "SEARCH=\"\$SEARCH $search\""
166			if ! $private; then
167				echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
168			fi
169			ns=
170			search=
171			new=true
172			;;
173		esac
174	done
175}
176
177uniqify()
178{
179	local result=
180	while [ -n "$1" ]; do
181		case " $result " in
182		*" $1 "*);;
183		*) result="$result $1";;
184		esac
185		shift
186	done
187	echo "${result# *}"
188}
189
190list_resolv()
191{
192	[ -d "$IFACEDIR" ] || return 0
193
194	local report=false list= retval=0 cmd="$1"
195	shift
196
197	# If we have an interface ordering list, then use that.
198	# It works by just using pathname expansion in the interface directory.
199	if [ -n "$1" ]; then
200		list="$@"
201		$force || report=true
202	else
203		cd "$IFACEDIR"
204		for i in $interface_order; do
205			[ -e "$i" ] && list="$list $i"
206		done
207		for i in $dynamic_order; do
208			if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
209				list="$list $i"
210			fi
211		done
212		if [ -d "$METRICDIR" ]; then
213			cd "$METRICDIR"
214			for i in *; do
215				list="$list ${i#* }"
216			done
217		fi
218		list="$list *"
219	fi
220
221	cd "$IFACEDIR"
222	for i in $(uniqify $list); do
223		# Only list interfaces which we really have
224		if ! [ -e "$i" ]; then
225			if $report; then
226				echo "No resolv.conf for interface $i" >&2
227				retval=$(($retval + 1))
228			fi
229			continue
230		fi
231
232		if [ "$cmd" = i -o "$cmd" = "-i" ]; then
233			printf "$i "
234		else
235			echo_resolv "$i"
236		fi
237	done
238	[ "$cmd" = i -o "$cmd" = "-i" ] && echo
239	return $retval
240}
241
242make_vars()
243{
244	eval "$(list_resolv -l "$@" | parse_resolv)"
245
246	# Ensure that we only list each domain once
247	newdomains=
248	for d in $DOMAINS; do
249		dn="${d%%:*}"
250		case " $newdomains" in
251		*" ${dn}:"*) continue;;
252		esac
253		newdomains="$newdomains${newdomains:+ }$dn:"
254		newns=
255		for nd in $DOMAINS; do
256			if [ "$dn" = "${nd%%:*}" ]; then
257				ns="${nd#*:}"
258				while [ -n "$ns" ]; do
259					case ",$newns," in
260					*,${ns%%,*},*) ;;
261					*) newns="$newns${newns:+,}${ns%%,*}";;
262					esac
263					[ "$ns" = "${ns#*,}" ] && break
264					ns="${ns#*,}"
265				done
266			fi
267		done
268		newdomains="$newdomains$newns"
269	done
270	echo "DOMAINS='$newdomains'"
271	echo "SEARCH='$(uniqify $SEARCH)'"
272	echo "NAMESERVERS='$(uniqify $NAMESERVERS)'"
273	echo "LOCALNAMESERVERS='$(uniqify $LOCALNAMESERVERS)'"
274}
275
276force=false
277while getopts a:d:fhIilm:puv OPT; do
278	case "$OPT" in
279	f) force=true;;
280	h) usage;;
281	m) IF_METRIC="$OPTARG";;
282	p) IF_PRIVATE=1;;
283	'?') ;;
284	*) cmd="$OPT"; iface="$OPTARG";;
285	esac
286done
287shift $(($OPTIND - 1))
288args="$iface${iface:+ }$@"
289
290# -I inits the state dir
291if [ "$cmd" = I ]; then
292	if [ -d "$VARDIR" ]; then
293		rm -rf "$VARDIR"/*
294	fi
295	exit $?
296fi
297
298# -l lists our resolv files, optionally for a specific interface
299if [ "$cmd" = l -o "$cmd" = i ]; then
300	list_resolv "$cmd" "$args"
301	exit $?
302fi
303
304# Not normally needed, but subscribers should be able to run independently
305if [ "$cmd" = v ]; then
306	make_vars "$iface"
307	exit $?
308fi
309
310# Test that we have valid options
311if [ "$cmd" = a -o "$cmd" = d ]; then
312	if [ -z "$iface" ]; then
313		usage "Interface not specified"
314	fi
315elif [ "$cmd" != u ]; then
316	[ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
317	usage
318fi
319if [ "$cmd" = a ]; then
320	for x in '/' \\ ' ' '*'; do
321		case "$iface" in
322		*[$x]*) error_exit "$x not allowed in interface name";;
323		esac
324	done
325	for x in '.' '-' '~'; do
326		case "$iface" in
327		[$x]*) error_exit \
328			"$x not allowed at start of interface name";;
329		esac
330	done
331	[ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
332fi
333
334if [ ! -d "$IFACEDIR" ]; then
335	if [ ! -d "$VARDIR" ]; then
336		if [ -L "$VARDIR" ]; then
337			dir="$(readlink "$VARDIR")"
338			# link maybe relative
339			cd "${VARDIR%/*}"
340			if ! mkdir -m 0755 -p "$dir"; then
341				error_exit "Failed to create needed" \
342					"directory $dir"
343			fi
344		else
345			if ! mkdir -m 0755 -p "$VARDIR"; then
346				error_exit "Failed to create needed" \
347					"directory $VARDIR"
348			fi
349		fi
350	fi
351	mkdir -m 0755 -p "$IFACEDIR" || \
352		error_exit "Failed to create needed directory $IFACEDIR"
353else
354	# Delete any existing information about the interface
355	if [ "$cmd" = d ]; then
356		cd "$IFACEDIR"
357		for i in $args; do
358			if [ "$cmd" = d -a ! -e "$i" ]; then
359				$force && continue
360				error_exit "No resolv.conf for" \
361					"interface $i"
362			fi
363			rm -f "$i" "$METRICDIR/"*" $i" \
364				"$PRIVATEDIR/$i" || exit $?
365		done
366	fi
367fi
368
369if [ "$cmd" = a ]; then
370	# Read resolv.conf from stdin
371	resolv="$(cat)"
372	# If what we are given matches what we have, then do nothing
373	if [ -e "$IFACEDIR/$iface" ]; then
374		if [ "$(echo "$resolv")" = \
375			"$(cat "$IFACEDIR/$iface")" ]
376		then
377			exit 0
378		fi
379		rm "$IFACEDIR/$iface"
380	fi
381	echo "$resolv" >"$IFACEDIR/$iface" || exit $?
382	[ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
383	rm -f "$METRICDIR/"*" $iface"
384	if [ -n "$IF_METRIC" ]; then
385		# Pad metric to 6 characters, so 5 is less than 10
386		while [ ${#IF_METRIC} -le 6 ]; do
387			IF_METRIC="0$IF_METRIC"
388		done
389		echo " " >"$METRICDIR/$IF_METRIC $iface"
390	fi
391	case "$IF_PRIVATE" in
392	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
393		if [ ! -d "$PRIVATEDIR" ]; then
394			[ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
395			mkdir "$PRIVATEDIR"
396		fi
397		[ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
398		;;
399	*)
400		if [ -e "$PRIVATEDIR/$iface" ]; then
401			rm -f "$PRIVATEDIR/$iface"
402		fi
403		;;
404	esac
405fi
406
407eval "$(make_vars)"
408export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
409: ${list_resolv:=list_resolv -l}
410retval=0
411for script in "$LIBEXECDIR"/*; do
412	if [ -f "$script" ]; then
413		if [ -x "$script" ]; then
414			"$script" "$cmd" "$iface"
415		else
416			(. "$script" "$cmd" "$iface")
417		fi
418		retval=$(($retval + $?))
419	fi
420done
421exit $retval
422