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="" 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 "resolv_conf=\"/dev/null\" # prevent updating ${resolv_conf}" 160 echo "unbound_conf=\"${forward_conf}\"" 161 echo "unbound_pid=\"${pidfile}\"" 162 echo "unbound_service=\"${service}\"" 163 # resolvconf(8) likes to restart rather than reload 164 echo "unbound_restart=\"service ${service} reload\"" 165} 166 167# 168# Generate forward.conf 169# 170gen_forward_conf() { 171 echo "# Generated by $self" 172 echo "forward-zone:" 173 echo " name: ." 174 for forwarder ; do 175 if expr "${forwarder}" : "^[0-9:.]\{1,\}$" >/dev/null ; then 176 echo " forward-addr: ${forwarder}" 177 else 178 echo " forward-host: ${forwarder}" 179 fi 180 done 181} 182 183# 184# Generate unbound.conf 185# 186gen_unbound_conf() { 187 echo "# Generated by $self" 188 echo "server:" 189 echo " username: ${user}" 190 echo " directory: ${workdir}" 191 echo " chroot: ${chrootdir}" 192 echo " pidfile: ${pidfile}" 193 echo " auto-trust-anchor-file: ${anchor}" 194 echo "" 195 if [ -f "${forward_conf}" ] ; then 196 echo "include: ${forward_conf}" 197 fi 198} 199 200# 201# Replace one file with another, making a backup copy of the first, 202# but only if the new file is different from the old. 203# 204replace() { 205 local file="$1" 206 local newfile="$2" 207 if [ ! -f "${file}" ] ; then 208 echo "${file} created" 209 mv "${newfile}" "${file}" 210 elif ! cmp -s "${file}" "${newfile}" ; then 211 local oldfile="${file}.${bkext}" 212 echo "original ${file} saved as ${oldfile}" 213 mv "${file}" "${oldfile}" 214 mv "${newfile}" "${file}" 215 else 216 echo "${file} not modified" 217 rm "${newfile}" 218 fi 219} 220 221# 222# Print usage message and exit 223# 224usage() { 225 exec >&2 226 echo "usage: $self [options] [forwarder ...]" 227 echo "options:" 228 echo " -n do not start unbound" 229 echo " -a path full path to trust anchor file" 230 echo " -c path full path to unbound configuration" 231 echo " -f path full path to forwarding configuration" 232 echo " -p path full path to pid file" 233 echo " -R path full path to resolvconf.conf" 234 echo " -r path full path to resolv.conf" 235 echo " -s service name of unbound service" 236 echo " -u user user to run unbound as" 237 echo " -w path full path to working directory" 238 exit 1 239} 240 241# 242# Main 243# 244main() { 245 umask 022 246 247 # 248 # Parse and validate command-line options 249 # 250 while getopts "a:c:f:np:R:r:s:u:w:" option ; do 251 case $option in 252 a) 253 anchor="$OPTARG" 254 ;; 255 c) 256 unbound_conf="$OPTARG" 257 ;; 258 f) 259 forward_conf="$OPTARG" 260 ;; 261 n) 262 start_unbound="no" 263 ;; 264 p) 265 pidfile="$OPTARG" 266 ;; 267 R) 268 resolvconf_conf="$OPTARG" 269 ;; 270 r) 271 resolv_conf="$OPTARG" 272 ;; 273 s) 274 service="$OPTARG" 275 ;; 276 u) 277 user="$OPTARG" 278 ;; 279 w) 280 workdir="$OPTARG" 281 ;; 282 *) 283 usage 284 ;; 285 esac 286 done 287 shift $((OPTIND-1)) 288 set_defaults 289 290 # 291 # Get the list of forwarders, either from the command line or 292 # from resolv.conf. 293 # 294 forwarders="$@" 295 if [ -z "$forwarders" ] ; then 296 echo "Extracting forwarders from ${resolv_conf}." 297 forwarders=$(get_nameservers <"${resolv_conf}") 298 fi 299 300 # 301 # Generate forward.conf. 302 # 303 if [ -z "${forwarders}" ] ; then 304 echo -n "No forwarders found in ${resolv_conf##*/}, " 305 if [ -f "${forward_conf}" ] ; then 306 echo "using existing ${forward_conf##*/}." 307 else 308 echo "unbound will recurse." 309 fi 310 else 311 local tmp_forward_conf=$(mktemp -u "${forward_conf}.XXXXX") 312 gen_forward_conf ${forwarders} >"${tmp_forward_conf}" 313 replace "${forward_conf}" "${tmp_forward_conf}" 314 fi 315 316 # 317 # Generate unbound.conf. 318 # 319 local tmp_unbound_conf=$(mktemp -u "${unbound_conf}.XXXXX") 320 set_chrootdir 321 gen_unbound_conf >"${tmp_unbound_conf}" 322 replace "${unbound_conf}" "${tmp_unbound_conf}" 323 324 # 325 # Start unbound, unless requested not to. Stop immediately if 326 # it is not enabled so we don't end up with a resolv.conf that 327 # points into nothingness. We could "onestart" it, but it 328 # wouldn't stick. 329 # 330 if [ "${start_unbound}" = "no" ] ; then 331 # skip 332 elif ! service "${service}" enabled ; then 333 echo "Please enable $service in rc.conf(5) and try again." 334 return 1 335 elif ! service "${service}" restart ; then 336 echo "Failed to start $service." 337 return 1 338 fi 339 340 # 341 # Rewrite resolvconf.conf so resolvconf updates forward.conf 342 # instead of resolv.conf. 343 # 344 local tmp_resolvconf_conf=$(mktemp -u "${resolvconf_conf}.XXXXX") 345 gen_resolvconf_conf >"${tmp_resolvconf_conf}" 346 replace "${resolvconf_conf}" "${tmp_resolvconf_conf}" 347 348 # 349 # Finally, rewrite resolv.conf. 350 # 351 local tmp_resolv_conf=$(mktemp -u "${resolv_conf}.XXXXX") 352 gen_resolv_conf <"${resolv_conf}" >"${tmp_resolv_conf}" 353 replace "${resolv_conf}" "${tmp_resolv_conf}" 354} 355 356main "$@" 357