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-cert-bundle: /etc/ssl/cert.pem" 263 fi 264 echo " so-sndbuf: 0" 265 echo "" 266 if [ -f "${forward_conf}" ] ; then 267 echo "include: ${forward_conf}" 268 fi 269 if [ -f "${lanzones_conf}" ] ; then 270 echo "include: ${lanzones_conf}" 271 fi 272 if [ -f "${control_conf}" ] ; then 273 echo "include: ${control_conf}" 274 fi 275 if [ -d "${confdir}" ] ; then 276 echo "include: ${confdir}/*.conf" 277 fi 278} 279 280# 281# Rename a file we are about to replace. 282# 283backup() { 284 local file="$1" 285 if [ -f "${D}${file}" ] ; then 286 local bkfile="${bkdir}/${file##*/}.${bkext}" 287 echo "Original ${file} saved as ${bkfile}" 288 mv "${D}${file}" "${D}${bkfile}" 289 fi 290} 291 292# 293# Wrapper for mktemp which respects DESTDIR 294# 295tmp() { 296 local file="$1" 297 mktemp -u "${D}${file}.XXXXX" 298} 299 300# 301# Replace one file with another, making a backup copy of the first, 302# but only if the new file is different from the old. 303# 304replace() { 305 local file="$1" 306 local newfile="$2" 307 if [ ! -f "${D}${file}" ] ; then 308 echo "${file} created" 309 mv "${newfile}" "${D}${file}" 310 elif ! cmp -s "${D}${file}" "${newfile}" ; then 311 backup "${file}" 312 mv "${newfile}" "${D}${file}" 313 else 314 echo "${file} not modified" 315 rm "${newfile}" 316 fi 317} 318 319# 320# Print usage message and exit 321# 322usage() { 323 exec >&2 324 echo "usage: $self [options] [forwarder ...]" 325 echo "options:" 326 echo " -n do not start unbound" 327 echo " -a path full path to trust anchor file" 328 echo " -C path full path to additional configuration directory" 329 echo " -c path full path to unbound configuration file" 330 echo " -f path full path to forwarding configuration" 331 echo " -O path full path to remote control socket" 332 echo " -o path full path to remote control configuration" 333 echo " -p path full path to pid file" 334 echo " -R path full path to resolvconf.conf" 335 echo " -r path full path to resolv.conf" 336 echo " -s service name of unbound service" 337 echo " -u user user to run unbound as" 338 echo " -w path full path to working directory" 339 exit 1 340} 341 342# 343# Main 344# 345main() { 346 umask 022 347 348 # 349 # Parse and validate command-line options 350 # 351 while getopts "a:C:c:f:no:p:R:r:s:tu:w:" option ; do 352 case $option in 353 a) 354 anchor="$OPTARG" 355 ;; 356 C) 357 confdir="$OPTARG" 358 ;; 359 c) 360 unbound_conf="$OPTARG" 361 ;; 362 f) 363 forward_conf="$OPTARG" 364 ;; 365 n) 366 start_unbound="no" 367 ;; 368 O) 369 control_socket="$OPTARG" 370 ;; 371 o) 372 control_conf="$OPTARG" 373 ;; 374 p) 375 pidfile="$OPTARG" 376 ;; 377 R) 378 resolvconf_conf="$OPTARG" 379 ;; 380 r) 381 resolv_conf="$OPTARG" 382 ;; 383 s) 384 service="$OPTARG" 385 ;; 386 t) 387 use_tls="yes" 388 ;; 389 u) 390 user="$OPTARG" 391 ;; 392 w) 393 workdir="$OPTARG" 394 ;; 395 *) 396 usage 397 ;; 398 esac 399 done 400 shift $((OPTIND-1)) 401 set_defaults 402 403 # 404 # Get the list of forwarders, either from the command line or 405 # from resolv.conf. 406 # 407 forwarders="$@" 408 case "${forwarders}" in 409 [Nn][Oo][Nn][Ee]) 410 forwarders="none" 411 style=recursing 412 ;; 413 "") 414 if [ -f "${D}${resolv_conf}" ] ; then 415 echo "Extracting forwarders from ${resolv_conf}." 416 forwarders=$(get_nameservers <"${D}${resolv_conf}") 417 fi 418 style=dynamic 419 ;; 420 *) 421 style=static 422 ;; 423 esac 424 425 # 426 # Generate forward.conf. 427 # 428 if [ -z "${forwarders}" ] ; then 429 echo -n "No forwarders found in ${resolv_conf##*/}, " 430 if [ -f "${forward_conf}" ] ; then 431 echo "using existing ${forward_conf##*/}." 432 else 433 echo "unbound will recurse." 434 fi 435 elif [ "${forwarders}" = "none" ] ; then 436 echo "Forwarding disabled, unbound will recurse." 437 backup "${forward_conf}" 438 else 439 local tmp_forward_conf=$(tmp "${forward_conf}") 440 gen_forward_conf ${forwarders} | unexpand >"${tmp_forward_conf}" 441 replace "${forward_conf}" "${tmp_forward_conf}" 442 fi 443 444 # 445 # Generate lan-zones.conf. 446 # 447 local tmp_lanzones_conf=$(tmp "${lanzones_conf}") 448 gen_lanzones_conf | unexpand >"${tmp_lanzones_conf}" 449 replace "${lanzones_conf}" "${tmp_lanzones_conf}" 450 451 # 452 # Generate control.conf. 453 # 454 local tmp_control_conf=$(tmp "${control_conf}") 455 gen_control_conf | unexpand >"${tmp_control_conf}" 456 replace "${control_conf}" "${tmp_control_conf}" 457 458 # 459 # Generate unbound.conf. 460 # 461 local tmp_unbound_conf=$(tmp "${unbound_conf}") 462 set_chrootdir 463 gen_unbound_conf | unexpand >"${tmp_unbound_conf}" 464 replace "${unbound_conf}" "${tmp_unbound_conf}" 465 466 # 467 # Start unbound, unless requested not to. Stop immediately if 468 # it is not enabled so we don't end up with a resolv.conf that 469 # points into nothingness. We could "onestart" it, but it 470 # wouldn't stick. 471 # 472 if [ "${start_unbound}" = "no" ] ; then 473 # skip 474 elif ! service "${service}" enabled ; then 475 echo "Please enable $service in rc.conf(5) and try again." 476 return 1 477 elif ! service "${service}" restart ; then 478 echo "Failed to start $service." 479 return 1 480 fi 481 482 # 483 # Rewrite resolvconf.conf so resolvconf updates forward.conf 484 # instead of resolv.conf. 485 # 486 local tmp_resolvconf_conf=$(tmp "${resolvconf_conf}") 487 gen_resolvconf_conf "${style}" | unexpand >"${tmp_resolvconf_conf}" 488 replace "${resolvconf_conf}" "${tmp_resolvconf_conf}" 489 490 # 491 # Finally, rewrite resolv.conf. 492 # 493 local tmp_resolv_conf=$(tmp "${resolv_conf}") 494 gen_resolv_conf <"${D}${resolv_conf}" | unexpand >"${tmp_resolv_conf}" 495 replace "${resolv_conf}" "${tmp_resolv_conf}" 496} 497 498main "$@" 499