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