local-unbound-setup.sh revision 269257
1184610Salfred#!/bin/sh
2184610Salfred#-
3184610Salfred# Copyright (c) 2013 Dag-Erling Sm��rgrav
4184610Salfred# All rights reserved.
5184610Salfred#
6184610Salfred# Redistribution and use in source and binary forms, with or without
7184610Salfred# modification, are permitted provided that the following conditions
8184610Salfred# are met:
9184610Salfred# 1. Redistributions of source code must retain the above copyright
10184610Salfred#    notice, this list of conditions and the following disclaimer.
11184610Salfred# 2. Redistributions in binary form must reproduce the above copyright
12184610Salfred#    notice, this list of conditions and the following disclaimer in the
13184610Salfred#    documentation and/or other materials provided with the distribution.
14184610Salfred#
15184610Salfred# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16184610Salfred# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17184610Salfred# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18184610Salfred# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19184610Salfred# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20184610Salfred# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21184610Salfred# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22184610Salfred# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23184610Salfred# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24184610Salfred# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25184610Salfred# SUCH DAMAGE.
26184610Salfred#
27194677Sthompsa# $FreeBSD: stable/10/usr.sbin/unbound/local-setup/local-unbound-setup.sh 269257 2014-07-29 20:57:38Z des $
28194677Sthompsa#
29194677Sthompsa
30194677Sthompsa#
31194677Sthompsa# Configuration variables
32194677Sthompsa#
33194677Sthompsauser=""
34194677Sthompsaunbound_conf=""
35194677Sthompsaforward_conf=""
36194677Sthompsalanzones_conf=""
37194677Sthompsaworkdir=""
38194677Sthompsaconfdir=""
39194677Sthompsachrootdir=""
40194677Sthompsaanchor=""
41194677Sthompsapidfile=""
42194677Sthompsaresolv_conf=""
43194677Sthompsaresolvconf_conf=""
44194677Sthompsaservice=""
45194677Sthompsastart_unbound=""
46188942Sthompsaforwarders=""
47194677Sthompsa
48184610Salfred#
49188942Sthompsa# Global variables
50188942Sthompsa#
51188942Sthompsaself=$(basename $(realpath "$0"))
52188942Sthompsabkext=$(date "+%Y%m%d.%H%M%S")
53184610Salfred
54184610Salfred#
55194228Sthompsa# Set default values for unset configuration variables.
56194228Sthompsa#
57194228Sthompsaset_defaults() {
58225469Shselasky	: ${user:=unbound}
59194228Sthompsa	: ${workdir:=/var/unbound}
60184610Salfred	: ${confdir:=${workdir}/conf.d}
61184610Salfred	: ${unbound_conf:=${workdir}/unbound.conf}
62194228Sthompsa	: ${forward_conf:=${workdir}/forward.conf}
63194228Sthompsa	: ${lanzones_conf:=${workdir}/lan-zones.conf}
64194228Sthompsa	: ${anchor:=${workdir}/root.key}
65194228Sthompsa	: ${pidfile:=/var/run/local_unbound.pid}
66194228Sthompsa	: ${resolv_conf:=/etc/resolv.conf}
67194228Sthompsa	: ${resolvconf_conf:=/etc/resolvconf.conf}
68184610Salfred	: ${service:=local_unbound}
69193045Sthompsa	: ${start_unbound:=yes}
70194228Sthompsa}
71184610Salfred
72184610Salfred#
73184610Salfred# Verify that the configuration files are inside the working
74184610Salfred# directory, and if so, set the chroot directory accordingly.
75225469Shselasky#
76194228Sthompsaset_chrootdir() {
77184610Salfred	chrootdir="${workdir}"
78184610Salfred	for file in "${unbound_conf}" "${forward_conf}" \
79184610Salfred	    "${lanzones_conf}" "${anchor}" ; do
80184610Salfred		if [ "${file#${workdir%/}/}" = "${file}" ] ; then
81184610Salfred			echo "warning: ${file} is outside ${workdir}" >&2
82194228Sthompsa			chrootdir=""
83184610Salfred		fi
84184610Salfred	done
85184610Salfred	if [ -z "${chrootdir}" ] ; then
86184610Salfred		echo "warning: disabling chroot" >&2
87193045Sthompsa	fi
88194228Sthompsa}
89184610Salfred
90184610Salfred#
91191402Sthompsa# Scan through /etc/resolv.conf looking for uncommented nameserver
92184610Salfred# lines that don't point to localhost and return their values.
93184610Salfred#
94184610Salfredget_nameservers() {
95194228Sthompsa	while read line ; do
96184610Salfred		local bareline=${line%%\#*}
97194228Sthompsa		local key=${bareline%% *}
98184610Salfred		local value=${bareline#* }
99194228Sthompsa		case ${key} in
100184610Salfred		nameserver)
101194228Sthompsa			case ${value} in
102184610Salfred			127.0.0.1|::1|localhost|localhost.*)
103184610Salfred				;;
104184610Salfred			*)
105184610Salfred				echo "${value}"
106194228Sthompsa				;;
107184610Salfred			esac
108184610Salfred			;;
109184610Salfred		esac
110194228Sthompsa	done
111194228Sthompsa}
112184610Salfred
113184610Salfred#
114184610Salfred# Scan through /etc/resolv.conf looking for uncommented nameserver
115184610Salfred# lines.  Comment out any that don't point to localhost.  Finally,
116184610Salfred# append a nameserver line that points to localhost, if there wasn't
117184610Salfred# one already, and enable the edns0 option.
118184610Salfred#
119184610Salfredgen_resolv_conf() {
120184610Salfred	local localhost=no
121194228Sthompsa	local edns0=no
122184610Salfred	while read line ; do
123184610Salfred		local bareline=${line%%\#*}
124184610Salfred		local key=${bareline%% *}
125194228Sthompsa		local value=${bareline#* }
126194228Sthompsa		case ${key} in
127194228Sthompsa		nameserver)
128184610Salfred			case ${value} in
129184610Salfred			127.0.0.1|::1|localhost|localhost.*)
130184610Salfred				localhost=yes
131184610Salfred				;;
132184610Salfred			*)
133184610Salfred				echo -n "# "
134184610Salfred				;;
135184610Salfred			esac
136184610Salfred			;;
137194228Sthompsa		options)
138184610Salfred			case ${value} in
139184610Salfred			*edns0*)
140184610Salfred				edns0=yes
141194228Sthompsa				;;
142184610Salfred			esac
143184610Salfred			;;
144184610Salfred		esac
145184610Salfred		echo "${line}"
146184610Salfred	done
147184610Salfred	if [ "${localhost}" = "no" ] ; then
148184610Salfred		echo "nameserver 127.0.0.1"
149	fi
150	if [ "${edns0}" = "no" ] ; then
151		echo "options edns0"
152	fi
153}
154
155#
156# Generate resolvconf.conf so it updates forward.conf in addition to
157# resolv.conf.  Note "in addition to" rather than "instead of",
158# because we still want it to update the domain name and search path
159# if they change.  Setting name_servers to "127.0.0.1" ensures that
160# the libc resolver will try unbound first.
161#
162gen_resolvconf_conf() {
163	echo "# Generated by $self"
164	echo "resolv_conf=\"/dev/null\" # prevent updating ${resolv_conf}"
165	echo "unbound_conf=\"${forward_conf}\""
166	echo "unbound_pid=\"${pidfile}\""
167	echo "unbound_service=\"${service}\""
168	# resolvconf(8) likes to restart rather than reload
169	echo "unbound_restart=\"service ${service} reload\""
170}
171
172#
173# Generate forward.conf
174#
175gen_forward_conf() {
176	echo "# Generated by $self"
177	echo "# Do not edit this file."
178	echo "forward-zone:"
179	echo "        name: ."
180	for forwarder ; do
181		if expr "${forwarder}" : "^[0-9:.]\{1,\}$" >/dev/null ; then
182			echo "        forward-addr: ${forwarder}"
183		else
184			echo "        forward-host: ${forwarder}"
185		fi
186	done
187}
188
189#
190# Generate lan-zones.conf
191#
192gen_lanzones_conf() {
193	echo "# Generated by $self"
194	echo "# Do not edit this file."
195	echo "server:"
196	echo "        # Unblock reverse lookups for LAN addresses"
197	echo "        unblock-lan-zones: yes"
198	echo "        domain-insecure: 10.in-addr.arpa."
199	echo "        domain-insecure: 127.in-addr.arpa."
200	echo "        domain-insecure: 16.172.in-addr.arpa."
201	echo "        domain-insecure: 17.172.in-addr.arpa."
202	echo "        domain-insecure: 18.172.in-addr.arpa."
203	echo "        domain-insecure: 19.172.in-addr.arpa."
204	echo "        domain-insecure: 20.172.in-addr.arpa."
205	echo "        domain-insecure: 21.172.in-addr.arpa."
206	echo "        domain-insecure: 22.172.in-addr.arpa."
207	echo "        domain-insecure: 23.172.in-addr.arpa."
208	echo "        domain-insecure: 24.172.in-addr.arpa."
209	echo "        domain-insecure: 25.172.in-addr.arpa."
210	echo "        domain-insecure: 26.172.in-addr.arpa."
211	echo "        domain-insecure: 27.172.in-addr.arpa."
212	echo "        domain-insecure: 28.172.in-addr.arpa."
213	echo "        domain-insecure: 29.172.in-addr.arpa."
214	echo "        domain-insecure: 30.172.in-addr.arpa."
215	echo "        domain-insecure: 31.172.in-addr.arpa."
216	echo "        domain-insecure: 168.192.in-addr.arpa."
217	echo "        domain-insecure: 254.169.in-addr.arpa."
218	echo "        domain-insecure: d.f.ip6.arpa."
219	echo "        domain-insecure: 8.e.ip6.arpa."
220	echo "        domain-insecure: 9.e.ip6.arpa."
221	echo "        domain-insecure: a.e.ip6.arpa."
222	echo "        domain-insecure: b.e.ip6.arpa."
223}
224
225#
226# Generate unbound.conf
227#
228gen_unbound_conf() {
229	echo "# Generated by $self"
230	echo "server:"
231	echo "        username: ${user}"
232	echo "        directory: ${workdir}"
233	echo "        chroot: ${chrootdir}"
234	echo "        pidfile: ${pidfile}"
235	echo "        auto-trust-anchor-file: ${anchor}"
236	echo ""
237	if [ -f "${forward_conf}" ] ; then
238		echo "include: ${forward_conf}"
239	fi
240	if [ -f "${lanzones_conf}" ] ; then
241		echo "include: ${lanzones_conf}"
242	fi
243	if [ -d "${confdir}" ] ; then
244		echo "include: ${confdir}/*.conf"
245	fi
246}
247
248#
249# Replace one file with another, making a backup copy of the first,
250# but only if the new file is different from the old.
251#
252replace() {
253	local file="$1"
254	local newfile="$2"
255	if [ ! -f "${file}" ] ; then
256		echo "${file} created"
257		mv "${newfile}" "${file}"
258	elif ! cmp -s "${file}" "${newfile}" ; then
259		local oldfile="${file}.${bkext}"
260		echo "original ${file} saved as ${oldfile}"
261		mv "${file}" "${oldfile}"
262		mv "${newfile}" "${file}"
263	else
264		echo "${file} not modified"
265		rm "${newfile}"
266	fi
267}
268
269#
270# Print usage message and exit
271#
272usage() {
273	exec >&2
274	echo "usage: $self [options] [forwarder ...]"
275	echo "options:"
276	echo "    -n          do not start unbound"
277	echo "    -a path     full path to trust anchor file"
278	echo "    -C path     full path to additional configuration directory"
279	echo "    -c path     full path to unbound configuration file"
280	echo "    -f path     full path to forwarding configuration"
281	echo "    -p path     full path to pid file"
282	echo "    -R path     full path to resolvconf.conf"
283	echo "    -r path     full path to resolv.conf"
284	echo "    -s service  name of unbound service"
285	echo "    -u user     user to run unbound as"
286	echo "    -w path     full path to working directory"
287	exit 1
288}
289
290#
291# Main
292#
293main() {
294	umask 022
295
296	#
297	# Parse and validate command-line options
298	#
299	while getopts "a:C:c:f:np:R:r:s:u:w:" option ; do
300		case $option in
301		a)
302			anchor="$OPTARG"
303			;;
304		C)
305			confdir="$OPTARG"
306			;;
307		c)
308			unbound_conf="$OPTARG"
309			;;
310		f)
311			forward_conf="$OPTARG"
312			;;
313		n)
314			start_unbound="no"
315			;;
316		p)
317			pidfile="$OPTARG"
318			;;
319		R)
320			resolvconf_conf="$OPTARG"
321			;;
322		r)
323			resolv_conf="$OPTARG"
324			;;
325		s)
326			service="$OPTARG"
327			;;
328		u)
329			user="$OPTARG"
330			;;
331		w)
332			workdir="$OPTARG"
333			;;
334		*)
335			usage
336			;;
337		esac
338	done
339	shift $((OPTIND-1))
340	set_defaults
341
342	#
343	# Get the list of forwarders, either from the command line or
344	# from resolv.conf.
345	#
346	forwarders="$@"
347	if [ -z "$forwarders" ] ; then
348		echo "Extracting forwarders from ${resolv_conf}."
349		forwarders=$(get_nameservers <"${resolv_conf}")
350	fi
351
352	#
353	# Generate forward.conf.
354	#
355	if [ -z "${forwarders}" ] ; then
356		echo -n "No forwarders found in ${resolv_conf##*/}, "
357		if [ -f "${forward_conf}" ] ; then
358			echo "using existing ${forward_conf##*/}."
359		else
360			echo "unbound will recurse."
361		fi
362	else
363		local tmp_forward_conf=$(mktemp -u "${forward_conf}.XXXXX")
364		gen_forward_conf ${forwarders} >"${tmp_forward_conf}"
365		replace "${forward_conf}" "${tmp_forward_conf}"
366	fi
367
368	#
369	# Generate lan-zones.conf.
370	#
371	local tmp_lanzones_conf=$(mktemp -u "${lanzones_conf}.XXXXX")
372	gen_lanzones_conf >"${tmp_lanzones_conf}"
373	replace "${lanzones_conf}" "${tmp_lanzones_conf}"
374
375	#
376	# Generate unbound.conf.
377	#
378	local tmp_unbound_conf=$(mktemp -u "${unbound_conf}.XXXXX")
379	set_chrootdir
380	gen_unbound_conf >"${tmp_unbound_conf}"
381	replace "${unbound_conf}" "${tmp_unbound_conf}"
382
383	#
384	# Start unbound, unless requested not to.  Stop immediately if
385	# it is not enabled so we don't end up with a resolv.conf that
386	# points into nothingness.  We could "onestart" it, but it
387	# wouldn't stick.
388	#
389	if [ "${start_unbound}" = "no" ] ; then
390		# skip
391	elif ! service "${service}" enabled ; then
392		echo "Please enable $service in rc.conf(5) and try again."
393		return 1
394	elif ! service "${service}" restart ; then
395		echo "Failed to start $service."
396		return 1
397	fi
398
399	#
400	# Rewrite resolvconf.conf so resolvconf updates forward.conf
401	# instead of resolv.conf.
402	#
403	local tmp_resolvconf_conf=$(mktemp -u "${resolvconf_conf}.XXXXX")
404	gen_resolvconf_conf >"${tmp_resolvconf_conf}"
405	replace "${resolvconf_conf}" "${tmp_resolvconf_conf}"
406
407	#
408	# Finally, rewrite resolv.conf.
409	#
410	local tmp_resolv_conf=$(mktemp -u "${resolv_conf}.XXXXX")
411	gen_resolv_conf <"${resolv_conf}" >"${tmp_resolv_conf}"
412	replace "${resolv_conf}" "${tmp_resolv_conf}"
413}
414
415main "$@"
416