local-unbound-setup.sh revision 266863
1#!/bin/sh
2#-
3# Copyright (c) 2013 Dag-Erling Sm��rgrav
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27# $FreeBSD: head/usr.sbin/unbound/local-setup/local-unbound-setup.sh 266863 2014-05-29 22:34:04Z des $
28#
29
30#
31# Configuration variables
32#
33user=""
34unbound_conf=""
35forward_conf=""
36workdir=""
37confdir=""
38chrootdir=""
39anchor=""
40pidfile=""
41resolv_conf=""
42resolvconf_conf=""
43service=""
44start_unbound=""
45forwarders=""
46
47#
48# Global variables
49#
50self=$(basename $(realpath "$0"))
51bkext=$(date "+%Y%m%d.%H%M%S")
52
53#
54# Set default values for unset configuration variables.
55#
56set_defaults() {
57	: ${user:=unbound}
58	: ${workdir:=/var/unbound}
59	: ${confdir:=${workdir}/conf.d}
60	: ${unbound_conf:=${workdir}/unbound.conf}
61	: ${forward_conf:=${workdir}/forward.conf}
62	: ${anchor:=${workdir}/root.key}
63	: ${pidfile:=/var/run/local_unbound.pid}
64	: ${resolv_conf:=/etc/resolv.conf}
65	: ${resolvconf_conf:=/etc/resolvconf.conf}
66	: ${service:=local_unbound}
67	: ${start_unbound:=yes}
68}
69
70#
71# Verify that the configuration files are inside the working
72# directory, and if so, set the chroot directory accordingly.
73#
74set_chrootdir() {
75	chrootdir="${workdir}"
76	for file in "${unbound_conf}" "${forward_conf}" "${anchor}" ; do
77		if [ "${file#${workdir%/}/}" = "${file}" ] ; then
78			echo "warning: ${file} is outside ${workdir}" >&2
79			chrootdir=""
80		fi
81	done
82	if [ -z "${chrootdir}" ] ; then
83		echo "warning: disabling chroot" >&2
84	fi
85}
86
87#
88# Scan through /etc/resolv.conf looking for uncommented nameserver
89# lines that don't point to localhost and return their values.
90#
91get_nameservers() {
92	while read line ; do
93		local bareline=${line%%\#*}
94		local key=${bareline%% *}
95		local value=${bareline#* }
96		case ${key} in
97		nameserver)
98			case ${value} in
99			127.0.0.1|::1|localhost|localhost.*)
100				;;
101			*)
102				echo "${value}"
103				;;
104			esac
105			;;
106		esac
107	done
108}
109
110#
111# Scan through /etc/resolv.conf looking for uncommented nameserver
112# lines.  Comment out any that don't point to localhost.  Finally,
113# append a nameserver line that points to localhost, if there wasn't
114# one already, and enable the edns0 option.
115#
116gen_resolv_conf() {
117	local localhost=no
118	local edns0=no
119	while read line ; do
120		local bareline=${line%%\#*}
121		local key=${bareline%% *}
122		local value=${bareline#* }
123		case ${key} in
124		nameserver)
125			case ${value} in
126			127.0.0.1|::1|localhost|localhost.*)
127				localhost=yes
128				;;
129			*)
130				echo -n "# "
131				;;
132			esac
133			;;
134		options)
135			case ${value} in
136			*edns0*)
137				edns0=yes
138				;;
139			esac
140			;;
141		esac
142		echo "${line}"
143	done
144	if [ "${localhost}" = "no" ] ; then
145		echo "nameserver 127.0.0.1"
146	fi
147	if [ "${edns0}" = "no" ] ; then
148		echo "options edns0"
149	fi
150}
151
152#
153# Generate resolvconf.conf so it updates forward.conf in addition to
154# resolv.conf.  Note "in addition to" rather than "instead of",
155# because we still want it to update the domain name and search path
156# if they change.  Setting name_servers to "127.0.0.1" ensures that
157# the libc resolver will try unbound first.
158#
159gen_resolvconf_conf() {
160	echo "# Generated by $self"
161	echo "resolv_conf=\"/dev/null\" # prevent updating ${resolv_conf}"
162	echo "unbound_conf=\"${forward_conf}\""
163	echo "unbound_pid=\"${pidfile}\""
164	echo "unbound_service=\"${service}\""
165	# resolvconf(8) likes to restart rather than reload
166	echo "unbound_restart=\"service ${service} reload\""
167}
168
169#
170# Generate forward.conf
171#
172gen_forward_conf() {
173	echo "# Generated by $self"
174	echo "forward-zone:"
175	echo "        name: ."
176	for forwarder ; do
177		if expr "${forwarder}" : "^[0-9:.]\{1,\}$" >/dev/null ; then
178			echo "        forward-addr: ${forwarder}"
179		else
180			echo "        forward-host: ${forwarder}"
181		fi
182	done
183}
184
185#
186# Generate unbound.conf
187#
188gen_unbound_conf() {
189	echo "# Generated by $self"
190	echo "server:"
191	echo "        username: ${user}"
192	echo "        directory: ${workdir}"
193	echo "        chroot: ${chrootdir}"
194	echo "        pidfile: ${pidfile}"
195	echo "        auto-trust-anchor-file: ${anchor}"
196	echo ""
197	if [ -f "${forward_conf}" ] ; then
198		echo "include: ${forward_conf}"
199	fi
200	if [ -d "${confdir}" ] ; then
201		echo "include: ${confdir}/*.conf"
202	fi
203}
204
205#
206# Replace one file with another, making a backup copy of the first,
207# but only if the new file is different from the old.
208#
209replace() {
210	local file="$1"
211	local newfile="$2"
212	if [ ! -f "${file}" ] ; then
213		echo "${file} created"
214		mv "${newfile}" "${file}"
215	elif ! cmp -s "${file}" "${newfile}" ; then
216		local oldfile="${file}.${bkext}"
217		echo "original ${file} saved as ${oldfile}"
218		mv "${file}" "${oldfile}"
219		mv "${newfile}" "${file}"
220	else
221		echo "${file} not modified"
222		rm "${newfile}"
223	fi
224}
225
226#
227# Print usage message and exit
228#
229usage() {
230	exec >&2
231	echo "usage: $self [options] [forwarder ...]"
232	echo "options:"
233	echo "    -n          do not start unbound"
234	echo "    -a path     full path to trust anchor file"
235	echo "    -C path     full path to additional configuration directory"
236	echo "    -c path     full path to unbound configuration file"
237	echo "    -f path     full path to forwarding configuration"
238	echo "    -p path     full path to pid file"
239	echo "    -R path     full path to resolvconf.conf"
240	echo "    -r path     full path to resolv.conf"
241	echo "    -s service  name of unbound service"
242	echo "    -u user     user to run unbound as"
243	echo "    -w path     full path to working directory"
244	exit 1
245}
246
247#
248# Main
249#
250main() {
251	umask 022
252
253	#
254	# Parse and validate command-line options
255	#
256	while getopts "a:C:c:f:np:R:r:s:u:w:" option ; do
257		case $option in
258		a)
259			anchor="$OPTARG"
260			;;
261		C)
262			confdir="$OPTARG"
263			;;
264		c)
265			unbound_conf="$OPTARG"
266			;;
267		f)
268			forward_conf="$OPTARG"
269			;;
270		n)
271			start_unbound="no"
272			;;
273		p)
274			pidfile="$OPTARG"
275			;;
276		R)
277			resolvconf_conf="$OPTARG"
278			;;
279		r)
280			resolv_conf="$OPTARG"
281			;;
282		s)
283			service="$OPTARG"
284			;;
285		u)
286			user="$OPTARG"
287			;;
288		w)
289			workdir="$OPTARG"
290			;;
291		*)
292			usage
293			;;
294		esac
295	done
296	shift $((OPTIND-1))
297	set_defaults
298
299	#
300	# Get the list of forwarders, either from the command line or
301	# from resolv.conf.
302	#
303	forwarders="$@"
304	if [ -z "$forwarders" ] ; then
305		echo "Extracting forwarders from ${resolv_conf}."
306		forwarders=$(get_nameservers <"${resolv_conf}")
307	fi
308
309	#
310	# Generate forward.conf.
311	#
312	if [ -z "${forwarders}" ] ; then
313		echo -n "No forwarders found in ${resolv_conf##*/}, "
314		if [ -f "${forward_conf}" ] ; then
315			echo "using existing ${forward_conf##*/}."
316		else
317			echo "unbound will recurse."
318		fi
319	else
320		local tmp_forward_conf=$(mktemp -u "${forward_conf}.XXXXX")
321		gen_forward_conf ${forwarders} >"${tmp_forward_conf}"
322		replace "${forward_conf}" "${tmp_forward_conf}"
323	fi
324
325	#
326	# Generate unbound.conf.
327	#
328	local tmp_unbound_conf=$(mktemp -u "${unbound_conf}.XXXXX")
329	set_chrootdir
330	gen_unbound_conf >"${tmp_unbound_conf}"
331	replace "${unbound_conf}" "${tmp_unbound_conf}"
332
333	#
334	# Start unbound, unless requested not to.  Stop immediately if
335	# it is not enabled so we don't end up with a resolv.conf that
336	# points into nothingness.  We could "onestart" it, but it
337	# wouldn't stick.
338	#
339	if [ "${start_unbound}" = "no" ] ; then
340		# skip
341	elif ! service "${service}" enabled ; then
342		echo "Please enable $service in rc.conf(5) and try again."
343		return 1
344	elif ! service "${service}" restart ; then
345		echo "Failed to start $service."
346		return 1
347	fi
348
349	#
350	# Rewrite resolvconf.conf so resolvconf updates forward.conf
351	# instead of resolv.conf.
352	#
353	local tmp_resolvconf_conf=$(mktemp -u "${resolvconf_conf}.XXXXX")
354	gen_resolvconf_conf >"${tmp_resolvconf_conf}"
355	replace "${resolvconf_conf}" "${tmp_resolvconf_conf}"
356
357	#
358	# Finally, rewrite resolv.conf.
359	#
360	local tmp_resolv_conf=$(mktemp -u "${resolv_conf}.XXXXX")
361	gen_resolv_conf <"${resolv_conf}" >"${tmp_resolv_conf}"
362	replace "${resolv_conf}" "${tmp_resolv_conf}"
363}
364
365main "$@"
366