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$ 28# 29 30# 31# Configuration variables 32# 33user="" 34unbound_conf="" 35forward_conf="" 36lanzones_conf="" 37workdir="" 38confdir="" 39chrootdir="" 40anchor="" 41pidfile="" 42resolv_conf="" 43resolvconf_conf="" 44service="" 45start_unbound="" 46forwarders="" 47 48# 49# Global variables 50# 51self=$(basename $(realpath "$0")) 52bkext=$(date "+%Y%m%d.%H%M%S") 53 54# 55# Set default values for unset configuration variables. 56# 57set_defaults() { 58 : ${user:=unbound} 59 : ${workdir:=/var/unbound} 60 : ${confdir:=${workdir}/conf.d} 61 : ${unbound_conf:=${workdir}/unbound.conf} 62 : ${forward_conf:=${workdir}/forward.conf} 63 : ${lanzones_conf:=${workdir}/lan-zones.conf} 64 : ${anchor:=${workdir}/root.key} 65 : ${pidfile:=/var/run/local_unbound.pid} 66 : ${resolv_conf:=/etc/resolv.conf} 67 : ${resolvconf_conf:=/etc/resolvconf.conf} 68 : ${service:=local_unbound} 69 : ${start_unbound:=yes} 70} 71 72# 73# Verify that the configuration files are inside the working 74# directory, and if so, set the chroot directory accordingly. 75# 76set_chrootdir() { 77 chrootdir="${workdir}" 78 for file in "${unbound_conf}" "${forward_conf}" \ 79 "${lanzones_conf}" "${anchor}" ; do 80 if [ "${file#${workdir%/}/}" = "${file}" ] ; then 81 echo "warning: ${file} is outside ${workdir}" >&2 82 chrootdir="" 83 fi 84 done 85 if [ -z "${chrootdir}" ] ; then 86 echo "warning: disabling chroot" >&2 87 fi 88} 89 90# 91# Scan through /etc/resolv.conf looking for uncommented nameserver 92# lines that don't point to localhost and return their values. 93# 94get_nameservers() { 95 while read line ; do 96 local bareline=${line%%\#*} 97 local key=${bareline%% *} 98 local value=${bareline#* } 99 case ${key} in 100 nameserver) 101 case ${value} in 102 127.0.0.1|::1|localhost|localhost.*) 103 ;; 104 *) 105 echo "${value}" 106 ;; 107 esac 108 ;; 109 esac 110 done 111} 112 113# 114# Scan through /etc/resolv.conf looking for uncommented nameserver 115# lines. Comment out any that don't point to localhost. Finally, 116# append a nameserver line that points to localhost, if there wasn't 117# one already, and enable the edns0 option. 118# 119gen_resolv_conf() { 120 local localhost=no 121 local edns0=no 122 while read line ; do 123 local bareline=${line%%\#*} 124 local key=${bareline%% *} 125 local value=${bareline#* } 126 case ${key} in 127 nameserver) 128 case ${value} in 129 127.0.0.1|::1|localhost|localhost.*) 130 localhost=yes 131 ;; 132 *) 133 echo -n "# " 134 ;; 135 esac 136 ;; 137 options) 138 case ${value} in 139 *edns0*) 140 edns0=yes 141 ;; 142 esac 143 ;; 144 esac 145 echo "${line}" 146 done 147 if [ "${localhost}" = "no" ] ; then 148 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-9A-Fa-f:.]\{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