local-unbound-setup.sh revision 255819
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 255819 2013-09-23 17:35:23Z des $
28#
29
30#
31# Configuration variables
32#
33user=""
34unbound_conf=""
35forward_conf=""
36workdir=""
37chrootdir=""
38anchor=""
39pidfile=""
40resolv_conf=""
41resolvconf_conf=""
42service=""
43start_unbound=""
44forwarders=""
45
46#
47# Global variables
48#
49self=$(basename $(realpath "$0"))
50bkext=$(date "+%Y%m%d.%H%M%S")
51
52#
53# Set default values for unset configuration variables.
54#
55set_defaults() {
56	: ${user:=unbound}
57	: ${workdir:=/var/unbound}
58	: ${unbound_conf:=${workdir}/unbound.conf}
59	: ${forward_conf:=${workdir}/forward.conf}
60	: ${anchor:=${workdir}/root.key}
61	: ${pidfile:=/var/run/local_unbound.pid}
62	: ${resolv_conf:=/etc/resolv.conf}
63	: ${resolvconf_conf:=/etc/resolvconf.conf}
64	: ${service:=local_unbound}
65	: ${start_unbound:=yes}
66}
67
68#
69# Verify that the configuration files are inside the working
70# directory, and if so, set the chroot directory accordingly.
71#
72set_chrootdir() {
73	chrootdir="${workdir}"
74	for file in "${unbound_conf}" "${forward_conf}" "${anchor}" ; do
75		if [ "${file#${workdir%/}/}" = "${file}" ] ; then
76			echo "warning: ${file} is outside ${workdir}" >&2
77			chrootdir=""
78		fi
79	done
80	if [ -z "${chrootdir}" ] ; then
81		echo "warning: disabling chroot" >&2
82	fi
83}
84
85#
86# Scan through /etc/resolv.conf looking for uncommented nameserver
87# lines that don't point to localhost and return their values.
88#
89get_nameservers() {
90	while read line ; do
91		local bareline=${line%%\#*}
92		local key=${bareline%% *}
93		local value=${bareline#* }
94		case ${key} in
95		nameserver)
96			case ${value} in
97			127.0.0.1|::1|localhost|localhost.*)
98				;;
99			*)
100				echo "${value}"
101				;;
102			esac
103			;;
104		esac
105	done
106}
107
108#
109# Scan through /etc/resolv.conf looking for uncommented nameserver
110# lines.  Comment out any that don't point to localhost.  Finally,
111# append a nameserver line that points to localhost, if there wasn't
112# one already, and enable the edns0 option.
113#
114gen_resolv_conf() {
115	local localhost=no
116	local edns0=no
117	while read line ; do
118		local bareline=${line%%\#*}
119		local key=${bareline%% *}
120		local value=${bareline#* }
121		case ${key} in
122		nameserver)
123			case ${value} in
124			127.0.0.1|::1|localhost|localhost.*)
125				localhost=yes
126				;;
127			*)
128				echo -n "# "
129				;;
130			esac
131			;;
132		options)
133			case ${value} in
134			*edns0*)
135				edns0=yes
136				;;
137			esac
138			;;
139		esac
140		echo "${line}"
141	done
142	if [ "${localhost}" = "no" ] ; then
143		echo "nameserver 127.0.0.1"
144	fi
145	if [ "${edns0}" = "no" ] ; then
146		echo "options edns0"
147	fi
148}
149
150#
151# Generate resolvconf.conf so it updates forward.conf in addition to
152# resolv.conf.  Note "in addition to" rather than "instead of",
153# because we still want it to update the domain name and search path
154# if they change.  Setting name_servers to "127.0.0.1" ensures that
155# the libc resolver will try unbound first.
156#
157gen_resolvconf_conf() {
158	echo "# Generated by $self"
159	echo "name_servers=\"127.0.0.1\""
160	echo "resolv_conf_options=\"edns0\""
161	echo "unbound_conf=\"${forward_conf}\""
162	echo "unbound_pid=\"${pidfile}\""
163	echo "unbound_service=\"${service}\""
164	# resolvconf(8) likes to restart rather than reload - consider
165	# forcing its hand?
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}
201
202#
203# Replace one file with another, making a backup copy of the first,
204# but only if the new file is different from the old.
205#
206replace() {
207	local file="$1"
208	local newfile="$2"
209	if [ ! -f "${file}" ] ; then
210		echo "${file} created"
211		mv "${newfile}" "${file}"
212	elif ! cmp -s "${file}" "${newfile}" ; then
213		local oldfile="${file}.${bkext}"
214		echo "original ${file} saved as ${oldfile}"
215		mv "${file}" "${oldfile}"
216		mv "${newfile}" "${file}"
217	else
218		echo "${file} not modified"
219		rm "${newfile}"
220	fi
221}
222
223#
224# Print usage message and exit
225#
226usage() {
227	exec >&2
228	echo "usage: $self [options] [forwarder ...]"
229	echo "options:"
230	echo "    -n          do not start unbound"
231	echo "    -a path     full path to trust anchor file"
232	echo "    -c path     full path to unbound configuration"
233	echo "    -f path     full path to forwarding configuration"
234	echo "    -p path     full path to pid file"
235	echo "    -R path     full path to resolvconf.conf"
236	echo "    -r path     full path to resolv.conf"
237	echo "    -s service  name of unbound service"
238	echo "    -u user     user to run unbound as"
239	echo "    -w path     full path to working directory"
240	exit 1
241}
242
243#
244# Main
245#
246main() {
247	umask 022
248
249	#
250	# Parse and validate command-line options
251	#
252	while getopts "a:c:f:np:R:r:s:u:w:" option ; do
253		case $option in
254		a)
255			anchor="$OPTARG"
256			;;
257		c)
258			unbound_conf="$OPTARG"
259			;;
260		f)
261			forward_conf="$OPTARG"
262			;;
263		n)
264			start_unbound="no"
265			;;
266		p)
267			pidfile="$OPTARG"
268			;;
269		R)
270			resolvconf_conf="$OPTARG"
271			;;
272		r)
273			resolv_conf="$OPTARG"
274			;;
275		s)
276			service="$OPTARG"
277			;;
278		u)
279			user="$OPTARG"
280			;;
281		w)
282			workdir="$OPTARG"
283			;;
284		*)
285			usage
286			;;
287		esac
288	done
289	shift $((OPTIND-1))
290	set_defaults
291
292	#
293	# Get the list of forwarders, either from the command line or
294	# from resolv.conf.
295	#
296	forwarders="$@"
297	if [ -z "$forwarders" ] ; then
298		echo "Extracting forwarders from ${resolv_conf}."
299		forwarders=$(get_nameservers <"${resolv_conf}")
300	fi
301
302	#
303	# Generate forward.conf.
304	#
305	if [ -z "${forwarders}" ] ; then
306		echo -n "No forwarders found in ${resolv_conf##*/}, "
307		if [ -f "${forward_conf}" ] ; then
308			echo "using existing ${forward_conf##*/}."
309		else
310			echo "unbound will recurse."
311		fi
312	else
313		local tmp_forward_conf=$(mktemp -u "${forward_conf}.XXXXX")
314		gen_forward_conf ${forwarders} >"${tmp_forward_conf}"
315		replace "${forward_conf}" "${tmp_forward_conf}"
316	fi
317
318	#
319	# Generate unbound.conf.
320	#
321	local tmp_unbound_conf=$(mktemp -u "${unbound_conf}.XXXXX")
322	set_chrootdir
323	gen_unbound_conf >"${tmp_unbound_conf}"
324	replace "${unbound_conf}" "${tmp_unbound_conf}"
325
326	#
327	# Start unbound, unless requested not to.  Stop immediately if
328	# it is not enabled so we don't end up with a resolv.conf that
329	# points into nothingness.  We could "onestart" it, but it
330	# wouldn't stick.
331	#
332	if [ "${start_unbound}" = "no" ] ; then
333		# skip
334	elif ! service "${service}" enabled ; then
335		echo "Please enable $service in rc.conf(5) and try again."
336		return 1
337	elif ! service "${service}" restart ; then
338		echo "Failed to start $service."
339		return 1
340	fi
341
342	#
343	# Rewrite resolvconf.conf so resolvconf updates forward.conf
344	# instead of resolv.conf.
345	#
346	local tmp_resolvconf_conf=$(mktemp -u "${resolvconf_conf}.XXXXX")
347	gen_resolvconf_conf >"${tmp_resolvconf_conf}"
348	replace "${resolvconf_conf}" "${tmp_resolvconf_conf}"
349
350	#
351	# Finally, rewrite resolv.conf.
352	#
353	local tmp_resolv_conf=$(mktemp -u "${resolv_conf}.XXXXX")
354	gen_resolv_conf <"${resolv_conf}" >"${tmp_resolv_conf}"
355	replace "${resolv_conf}" "${tmp_resolv_conf}"
356}
357
358main "$@"
359