1#!/bin/sh
2# Copyright 2007 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
27argv0="$0"
28
29PREFIX=
30RESOLVCONF="${PREFIX}"/etc/resolvconf
31UPDATED="${RESOLVCONF}"/update.d
32VARDIR="${RESOLVCONF}"/run
33IFACEDIR="${VARDIR}/interfaces"
34
35error_exit() {
36	echo "$*" >&2
37	exit 1
38}
39
40usage() {
41	cat <<-EOF
42	Usage: ${argv0##*/} [options]
43
44	Inform the system about any DNS updates.
45
46	Options:
47	  -a \$INTERFACE    Add DNS information to the specified interface
48	                   (DNS supplied via stdin in resolv.conf format)
49	  -d \$INTERFACE    Delete DNS information from the specified interface
50	  -u               Run updates from our current DNS information
51	  -l [\$PATTERN]    Show DNS information, optionally from interfaces
52	                   that match the specified pattern
53	  -i [\$PATTERN]    Show interfaces that have supplied DNS information
54                   optionally from interfaces that match the specified
55                   pattern
56	  -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
57	  		   the console
58	  -s \$SVC \$CMD     Do \$CMD for the system service \$SVC
59	  -h               Show this help cruft
60	EOF
61	[ -z "$@" ] && exit 0
62	echo
63	error_exit "$*"
64}
65
66echo_resolv() {
67	[ -n "$1" -a -e "${IFACEDIR}/$1" ] || return 1
68	echo "# resolv.conf for interface $1"
69	cat "${IFACEDIR}/$1"
70	echo
71}
72
73uniqify() {
74    local result=
75    while [ -n "$1" ]; do
76		case " ${result} " in
77			*" $1 "*);;
78			*) result="${result} $1";;
79		esac
80		shift
81	done
82    echo "${result# *}"
83}
84
85if [ -n "$1" ]; then
86	CMD="$1"
87	shift
88fi
89
90# We do our service restarting here so that our subscribers don't have to know
91# about the OS's init system.
92if [ "x${CMD}" = "x-s" ]; then
93	if [ -n "$1" ]; then
94		SERVICE="$1"
95		shift
96	fi
97	[ -z "${SERVICE}" ] && usage "Service not specified"
98	if [ -n "$1" ]; then
99		ACTION="$1"
100		shift
101	fi
102	[ -z "${ACTION}" ] && usage "Action not specified"
103
104	# If restarting check if service is running or not if we can
105	if [ "x${ACTION}" = "xrestart" ]; then
106		if [ -s /var/run/"${SERVICE}".pid ]; then
107			kill -0 $(cat /var/run/"${SERVICE}".pid)
108		elif [ -s /var/run/"${SERVICE}"/"${SERVICE}".pid ]; then
109			kill -0 $(cat /var/run/"${SERVICE}".pid)
110		elif [ -s /var/run/"${SERVICE}"/pid ]; then
111			kill -0 $(cat /var/run/"${SERVICE}"/pid)
112		else
113			false
114		fi
115		# Service not running, so don't restart
116		[ $? != 0 ] && exit 1
117	fi
118	if [ -x /sbin/service ]; then
119		service "${SERVICE}" "${ACTION}" "$@"
120	elif [ -x /etc/init.d/"${SERVICE}" -a -x /sbin/runscript ]; then
121		if [ "x${ACTION}" = "xrestart" ]; then
122			/etc/init.d/"${SERVICE}" --quiet --nodeps conditionalrestart "$@"
123		else
124			/etc/init.d/"${SERVICE}" --quiet --nodeps "${ACTION}" "$@"
125		fi
126	elif [ -x /etc/init.d/"${SERVICE}" ]; then
127		/etc/init.d/"${SERVICE}" "${ACTION}" "$@"
128	elif [ -x /etc/rc.d/"${SERVICE}" ]; then
129		/etc/rc.d/"${SERVICE}" "${ACTION}" "$@"
130	elif [ -x /etc/rc.d/rc."${SERVICE}" ]; then
131		/etc/rc.d/rc."${SERVICE}" "${ACTION}" "$@"
132	else
133		error_exit "Don't know how to interact with services on this platform"
134	fi
135	exit $?
136fi
137
138if [ -n "$1" ]; then
139	IFACE="$1"
140	shift
141fi
142
143# -l lists our resolv files, optionally for a specific interface
144if [ "x${CMD}" = "x-l" -o "x${CMD}" = "x-i" ]; then
145	[ -d "${IFACEDIR}" ] || exit 0
146
147	# If we have an interface ordering list, then use that.
148	# It works by just using pathname expansion in the interface directory.
149	if [ -n "${IFACE}" ]; then
150		LIST="${IFACE} $@"
151	elif [ -r "${RESOLVCONF}"/interface-order ]; then
152		LIST="$(cat "${RESOLVCONF}"/interface-order)"
153	fi
154
155	# If we don't have a list then prefer lo, tunnels, ppp
156	# and then anything else.
157	if [ -z "${LIST}" ]; then
158		LIST="lo lo[0-9]* tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]* *"
159	fi
160
161	cd "${IFACEDIR}"
162	for IFACE in $(uniqify ${LIST}); do
163		# Only list interfaces which we really have
164		[ -e "${IFACE}" ] || continue
165
166		if [ "x${CMD}" = "x-i" ]; then
167			printf "${IFACE} "
168		else
169			echo_resolv "${IFACE}"
170		fi
171	done
172	[ "x${CMD}" = "x-i" ] && echo
173	exit 0
174fi
175
176if [ "x${CMD}" = "x-v" ]; then
177	NS=
178	DOMAIN=
179	SEARCH=
180	NEWSEARCH=
181	NEWNS=
182	NEWDOMAIN=
183	LINES="$("${argv0}" -l "${IFACE}" | sed -e "s/'/'\\\\''/g" -e "s/^/'/g" -e "s/$/'/g")"
184	eval set -- ${LINES}
185	for LINE in "$@"; do
186		case "${LINE}" in
187			"nameserver "*)
188				case "${LINE#* }" in
189					127.*) continue;;
190				esac
191				NS="${NS}${LINE#* } "
192				;;
193			"domain "*)
194				[ -z "${SEARCH}" ] && DOMAIN="${LINE#* }"
195				;;
196			"search "*)
197				SEARCH="${LINE#* }"
198				DOMAIN=
199				;;
200			*)
201				if [ -z "${LINE}" ]; then
202					for N in ${NS}; do
203						if [ -n "${DOMAIN}" ]; then
204							NEWDOMAIN="${NEWDOMAIN} ${DOMAIN},${N}"
205						elif [ -n "${SEARCH}" ]; then
206							for S in ${SEARCH}; do
207								NEWSEARCH="${NEWSEARCH} ${S},${N}"
208							done
209						else
210							NEWNS="${NEWNS} ${N}"
211						fi
212					done
213					NS=
214					DOMAIN=
215					SEARCH=
216				fi
217				;;
218		esac
219	done
220
221	# Prefer DOMAIN nameservers over SEARCH nameservers
222	# if we are supplied both.
223	NEWDOMAIN="$(uniqify ${NEWDOMAIN})"
224	NEWSEARCH="$(uniqify ${NEWSEARCH})"
225	NEWNS="$(uniqify ${NEWNS})"
226	for S in ${NEWSEARCH}; do
227		for DN in ${NEWDOMAIN}; do
228			if [ "${S%,*}" = "${DN%,*}" ]; then
229				NEWSEARCH="$(echo "${NEWSEARCH}" | sed -e "s/${S}/${DN}/g")"
230				NEWDOMAIN="$(echo "${NEWDOMAIN}" | sed -e "s/${DN}//g")"
231				break
232			fi
233		done
234	done
235
236	echo "NEWDOMAIN='${NEWDOMAIN}'"
237	echo "NEWSEARCH='${NEWSEARCH}'"
238	echo "NEWNS='${NEWNS}'"
239	exit 0
240fi
241
242# Test that we have valid options
243if [ "x${CMD}" = "x-a" -o "x${CMD}" = "x-d" ]; then
244	if [ -z "${IFACE}" ]; then
245		usage "Interface not specified"
246	fi
247elif [ "x${CMD}" != "x-u" ]; then
248	[ -n "x${CMD}" -a "x${CMD}" != "x-h" ] && usage "Unknown option ${CMD}"
249	usage
250fi
251if [ "x${CMD}" = "x-a" ]; then
252	for x in '/' \\ ' ' '*'; do
253		case "${IFACE}" in
254			*[${x}]*) error_exit "${x} not allowed in interface name";;
255		esac
256	done
257	for x in '.' '-' '~'; do
258		case "${IFACE}" in
259			[${x}]*) error_exit "${x} not allowed at start of interface name";;
260		esac
261	done
262	[ "x${CMD}" = "x-a" -a -t 0 ] && error_exit "No file given via stdin"
263	IFACERESOLV="${IFACEDIR}/${IFACE}"
264fi
265
266# Ensure that libdir exists
267if [ ! -d "${IFACEDIR}" ]; then
268	if [ ! -d "${VARDIR}" ]; then
269		if [ -L "${VARDIR}" ]; then
270			DIR="$(readlink "${VARDIR}")"
271			# Change to /etc as link maybe relative
272			cd "${VARDIR%/*}"
273			if ! mkdir -m 0755 -p "${DIR}"; then
274				error_exit "Failed to create needed directory ${DIR}"
275			fi
276		else
277			if ! mkdir -m 0755 -p "${VARDIR}"; then
278				error_exit "Failed to create needed directory ${VARDIR}"
279			fi
280		fi
281	fi
282	mkdir -m 0755 -p "${IFACEDIR}" || \
283		error_exit "Failed to create needed directory ${IFACEDIR}"
284else
285	# Delete any existing information about the interface
286	if [ "x${CMD}" = "x-a" -o "x${CMD}" = "x-d" ]; then
287		cd "${IFACEDIR}"
288		for iface in ${IFACE}; do
289			rm -f "${iface}" || exit $?
290		done
291	fi
292fi
293
294if [ "x${CMD}" = "x-a" ]; then
295	# Create our resolv.conf file
296	cat >"${IFACEDIR}"/"${IFACE}" || exit $?
297fi
298
299retval=0
300for x in "${UPDATED}"/*; do
301	if [ -e "${x}" ]; then
302		"${x}" "${CMD}" "${IFACE}"
303		retval=$((${retval} + $?))
304	fi
305done
306
307exit ${retval}
308
309# vim: set ts=4 :
310