1#!/bin/sh 2#- 3# SPDX-License-Identifier: BSD-2-Clause 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# 30 31D="${DESTDIR}" 32echo "destination: ${D}" 33 34# 35# Configuration variables 36# 37user="" 38unbound_conf="" 39forward_conf="" 40lanzones_conf="" 41control_conf="" 42control_socket="" 43workdir="" 44confdir="" 45chrootdir="" 46anchor="" 47pidfile="" 48resolv_conf="" 49resolvconf_conf="" 50service="" 51start_unbound="" 52use_tls="" 53forwarders="" 54 55# 56# Global variables 57# 58self=$(basename $(realpath "$0")) 59bkdir=/var/backups 60bkext=$(date "+%Y%m%d.%H%M%S") 61 62# 63# Regular expressions 64# 65RE_octet="([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])" 66RE_ipv4="(${RE_octet}(\\.${RE_octet}){3})" 67RE_word="([0-9A-Fa-f]{1,4})" 68RE_ipv6="((${RE_word}:){1,}(:|${RE_word}?(:${RE_word})*)|::1)" 69RE_port="([1-9][0-9]{0,3}|[1-5][0-9]{4,4}|6([0-4][0-9]{3}|5([0-4][0-9]{2}|5([0-2][0-9]|3[0-5]))))" 70RE_dnsname="([0-9A-Za-z-]{1,}(\\.[0-9A-Za-z-]{1,})*\\.?)" 71RE_forward_addr="((${RE_ipv4}|${RE_ipv6})(@${RE_port})?)" 72RE_forward_name="(${RE_dnsname}(@${RE_port})?)" 73RE_forward_tls="(${RE_forward_addr}(#${RE_dnsname})?)" 74 75# 76# Set default values for unset configuration variables. 77# 78set_defaults() { 79 : ${user:=unbound} 80 : ${workdir:=/var/unbound} 81 : ${confdir:=${workdir}/conf.d} 82 : ${unbound_conf:=${workdir}/unbound.conf} 83 : ${forward_conf:=${workdir}/forward.conf} 84 : ${lanzones_conf:=${workdir}/lan-zones.conf} 85 : ${control_conf:=${workdir}/control.conf} 86 : ${control_socket:=/var/run/local_unbound.ctl} 87 : ${anchor:=${workdir}/root.key} 88 : ${pidfile:=/var/run/local_unbound.pid} 89 : ${resolv_conf:=/etc/resolv.conf} 90 : ${resolvconf_conf:=/etc/resolvconf.conf} 91 : ${service:=local_unbound} 92 : ${start_unbound:=yes} 93 : ${use_tls:=no} 94} 95 96# 97# Verify that the configuration files are inside the working 98# directory, and if so, set the chroot directory accordingly. 99# 100set_chrootdir() { 101 chrootdir="${workdir}" 102 for file in "${unbound_conf}" "${forward_conf}" \ 103 "${lanzones_conf}" "${control_conf}" "${anchor}" ; do 104 if [ "${file#${workdir%/}/}" = "${file}" ] ; then 105 echo "warning: ${file} is outside ${workdir}" >&2 106 chrootdir="" 107 fi 108 done 109 if [ -z "${chrootdir}" ] ; then 110 echo "warning: disabling chroot" >&2 111 fi 112} 113 114# 115# Scan through /etc/resolv.conf looking for uncommented nameserver 116# lines that don't point to localhost and return their values. 117# 118get_nameservers() { 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 ;; 128 *) 129 echo "${value}" 130 ;; 131 esac 132 ;; 133 esac 134 done 135} 136 137# 138# Scan through /etc/resolv.conf looking for uncommented nameserver 139# lines. Comment out any that don't point to localhost. Finally, 140# append a nameserver line that points to localhost, if there wasn't 141# one already, and enable the edns0 option. 142# 143gen_resolv_conf() { 144 local localhost=no 145 local edns0=no 146 while read line ; do 147 local bareline=${line%%\#*} 148 local key=${bareline%% *} 149 local value=${bareline#* } 150 case ${key} in 151 nameserver) 152 case ${value} in 153 127.0.0.1|::1|localhost|localhost.*) 154 localhost=yes 155 ;; 156 *) 157 echo -n "# " 158 ;; 159 esac 160 ;; 161 options) 162 case ${value} in 163 *edns0*) 164 edns0=yes 165 ;; 166 esac 167 ;; 168 esac 169 echo "${line}" 170 done 171 if [ "${localhost}" = "no" ] ; then 172 echo "nameserver 127.0.0.1" 173 fi 174 if [ "${edns0}" = "no" ] ; then 175 echo "options edns0" 176 fi 177} 178 179# 180# Boilerplate 181# 182do_not_edit() { 183 echo "# This file was generated by $self." 184 echo "# Modifications will be overwritten." 185} 186 187# 188# Generate resolvconf.conf so it updates forward.conf in addition to 189# resolv.conf. Note "in addition to" rather than "instead of", 190# because we still want it to update the domain name and search path 191# if they change. Setting name_servers to "127.0.0.1" ensures that 192# the libc resolver will try unbound first. 193# 194gen_resolvconf_conf() { 195 local style="$1" 196 do_not_edit 197 echo "libc=\"NO\"" 198 if [ "${style}" = "dynamic" ] ; then 199 echo "unbound_conf=\"${forward_conf}\"" 200 echo "unbound_pid=\"${pidfile}\"" 201 echo "unbound_service=\"${service}\"" 202 # resolvconf(8) likes to restart rather than reload 203 echo "unbound_restart=\"service ${service} reload\"" 204 else 205 echo "# Static DNS configuration" 206 fi 207} 208 209# 210# Generate forward.conf 211# 212gen_forward_conf() { 213 do_not_edit 214 echo "forward-zone:" 215 echo " name: ." 216 for forwarder ; do echo "${forwarder}" ; done | 217 if [ "${use_tls}" = "yes" ] ; then 218 echo " forward-tls-upstream: yes" 219 sed -nE \ 220 -e "s/^${RE_forward_tls}\$/ forward-addr: \\1/p" 221 else 222 sed -nE \ 223 -e "s/^${RE_forward_addr}\$/ forward-addr: \\1/p" \ 224 -e "s/^${RE_forward_name}\$/ forward-host: \\1/p" 225 fi 226} 227 228# 229# Generate lan-zones.conf 230# 231gen_lanzones_conf() { 232 do_not_edit 233 echo "server:" 234 echo " # Unblock reverse lookups for LAN addresses" 235 echo " unblock-lan-zones: yes" 236 echo " insecure-lan-zones: yes" 237} 238 239# 240# Generate control.conf 241# 242gen_control_conf() { 243 do_not_edit 244 echo "remote-control:" 245 echo " control-enable: yes" 246 echo " control-interface: ${control_socket}" 247 echo " control-use-cert: no" 248} 249 250# 251# Generate unbound.conf 252# 253gen_unbound_conf() { 254 do_not_edit 255 echo "server:" 256 echo " username: ${user}" 257 echo " directory: ${workdir}" 258 echo " chroot: ${chrootdir}" 259 echo " pidfile: ${pidfile}" 260 echo " auto-trust-anchor-file: ${anchor}" 261 if [ "${use_tls}" = "yes" ] ; then 262 echo " tls-system-cert: yes" 263 fi 264 echo "" 265 if [ -f "${forward_conf}" ] ; then 266 echo "include: ${forward_conf}" 267 fi 268 if [ -f "${lanzones_conf}" ] ; then 269 echo "include: ${lanzones_conf}" 270 fi 271 if [ -f "${control_conf}" ] ; then 272 echo "include: ${control_conf}" 273 fi 274 if [ -d "${confdir}" ] ; then 275 echo "include: ${confdir}/*.conf" 276 fi 277} 278 279# 280# Rename a file we are about to replace. 281# 282backup() { 283 local file="$1" 284 if [ -f "${D}${file}" ] ; then 285 local bkfile="${bkdir}/${file##*/}.${bkext}" 286 echo "Original ${file} saved as ${bkfile}" 287 mv "${D}${file}" "${D}${bkfile}" 288 fi 289} 290 291# 292# Wrapper for mktemp which respects DESTDIR 293# 294tmp() { 295 local file="$1" 296 mktemp -u "${D}${file}.XXXXX" 297} 298 299# 300# Replace one file with another, making a backup copy of the first, 301# but only if the new file is different from the old. 302# 303replace() { 304 local file="$1" 305 local newfile="$2" 306 if [ ! -f "${D}${file}" ] ; then 307 echo "${file} created" 308 mv "${newfile}" "${D}${file}" 309 elif ! cmp -s "${D}${file}" "${newfile}" ; then 310 backup "${file}" 311 mv "${newfile}" "${D}${file}" 312 else 313 echo "${file} not modified" 314 rm "${newfile}" 315 fi 316} 317 318# 319# Print usage message and exit 320# 321usage() { 322 exec >&2 323 echo "usage: $self [options] [forwarder ...]" 324 echo "options:" 325 echo " -n do not start unbound" 326 echo " -a path full path to trust anchor file" 327 echo " -C path full path to additional configuration directory" 328 echo " -c path full path to unbound configuration file" 329 echo " -f path full path to forwarding configuration" 330 echo " -O path full path to remote control socket" 331 echo " -o path full path to remote control configuration" 332 echo " -p path full path to pid file" 333 echo " -R path full path to resolvconf.conf" 334 echo " -r path full path to resolv.conf" 335 echo " -s service name of unbound service" 336 echo " -u user user to run unbound as" 337 echo " -w path full path to working directory" 338 exit 1 339} 340 341# 342# Main 343# 344main() { 345 umask 022 346 347 # 348 # Parse and validate command-line options 349 # 350 while getopts "a:C:c:f:no:p:R:r:s:tu:w:" option ; do 351 case $option in 352 a) 353 anchor="$OPTARG" 354 ;; 355 C) 356 confdir="$OPTARG" 357 ;; 358 c) 359 unbound_conf="$OPTARG" 360 ;; 361 f) 362 forward_conf="$OPTARG" 363 ;; 364 n) 365 start_unbound="no" 366 ;; 367 O) 368 control_socket="$OPTARG" 369 ;; 370 o) 371 control_conf="$OPTARG" 372 ;; 373 p) 374 pidfile="$OPTARG" 375 ;; 376 R) 377 resolvconf_conf="$OPTARG" 378 ;; 379 r) 380 resolv_conf="$OPTARG" 381 ;; 382 s) 383 service="$OPTARG" 384 ;; 385 t) 386 use_tls="yes" 387 ;; 388 u) 389 user="$OPTARG" 390 ;; 391 w) 392 workdir="$OPTARG" 393 ;; 394 *) 395 usage 396 ;; 397 esac 398 done 399 shift $((OPTIND-1)) 400 set_defaults 401 402 # 403 # Get the list of forwarders, either from the command line or 404 # from resolv.conf. 405 # 406 forwarders="$@" 407 case "${forwarders}" in 408 [Nn][Oo][Nn][Ee]) 409 forwarders="none" 410 style=recursing 411 ;; 412 "") 413 if [ -f "${D}${resolv_conf}" ] ; then 414 echo "Extracting forwarders from ${resolv_conf}." 415 forwarders=$(get_nameservers <"${D}${resolv_conf}") 416 fi 417 style=dynamic 418 ;; 419 *) 420 style=static 421 ;; 422 esac 423 424 # 425 # Generate forward.conf. 426 # 427 if [ -z "${forwarders}" ] ; then 428 echo -n "No forwarders found in ${resolv_conf##*/}, " 429 if [ -f "${forward_conf}" ] ; then 430 echo "using existing ${forward_conf##*/}." 431 else 432 echo "unbound will recurse." 433 fi 434 elif [ "${forwarders}" = "none" ] ; then 435 echo "Forwarding disabled, unbound will recurse." 436 backup "${forward_conf}" 437 else 438 local tmp_forward_conf=$(tmp "${forward_conf}") 439 gen_forward_conf ${forwarders} | unexpand >"${tmp_forward_conf}" 440 replace "${forward_conf}" "${tmp_forward_conf}" 441 fi 442 443 # 444 # Generate lan-zones.conf. 445 # 446 local tmp_lanzones_conf=$(tmp "${lanzones_conf}") 447 gen_lanzones_conf | unexpand >"${tmp_lanzones_conf}" 448 replace "${lanzones_conf}" "${tmp_lanzones_conf}" 449 450 # 451 # Generate control.conf. 452 # 453 local tmp_control_conf=$(tmp "${control_conf}") 454 gen_control_conf | unexpand >"${tmp_control_conf}" 455 replace "${control_conf}" "${tmp_control_conf}" 456 457 # 458 # Generate unbound.conf. 459 # 460 local tmp_unbound_conf=$(tmp "${unbound_conf}") 461 set_chrootdir 462 gen_unbound_conf | unexpand >"${tmp_unbound_conf}" 463 replace "${unbound_conf}" "${tmp_unbound_conf}" 464 465 # 466 # Start unbound, unless requested not to. Stop immediately if 467 # it is not enabled so we don't end up with a resolv.conf that 468 # points into nothingness. We could "onestart" it, but it 469 # wouldn't stick. 470 # 471 if [ "${start_unbound}" = "no" ] ; then 472 # skip 473 elif ! service "${service}" enabled ; then 474 echo "Please enable $service in rc.conf(5) and try again." 475 return 1 476 elif ! service "${service}" restart ; then 477 echo "Failed to start $service." 478 return 1 479 fi 480 481 # 482 # Rewrite resolvconf.conf so resolvconf updates forward.conf 483 # instead of resolv.conf. 484 # 485 local tmp_resolvconf_conf=$(tmp "${resolvconf_conf}") 486 gen_resolvconf_conf "${style}" | unexpand >"${tmp_resolvconf_conf}" 487 replace "${resolvconf_conf}" "${tmp_resolvconf_conf}" 488 489 # 490 # Finally, rewrite resolv.conf. 491 # 492 local tmp_resolv_conf=$(tmp "${resolv_conf}") 493 gen_resolv_conf <"${D}${resolv_conf}" | unexpand >"${tmp_resolv_conf}" 494 replace "${resolv_conf}" "${tmp_resolv_conf}" 495} 496 497main "$@" 498