1#!/bin/sh 2# Copyright (c) 2007-2012 Roy Marples 3# All rights reserved 4 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions 7# are met: 8# * Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# * Redistributions in binary form must reproduce the above 11# copyright notice, this list of conditions and the following 12# disclaimer in the documentation and/or other materials provided 13# with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 27RESOLVCONF="$0" 28SYSCONFDIR=@SYSCONFDIR@ 29LIBEXECDIR=@LIBEXECDIR@ 30VARDIR=@VARDIR@ 31 32# Disregard dhcpcd setting 33unset interface_order state_dir 34 35# Support original resolvconf configuration layout 36# as well as the openresolv config file 37if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then 38 . "$SYSCONFDIR"/resolvconf.conf 39 [ -n "$state_dir" ] && VARDIR="$state_dir" 40elif [ -d "$SYSCONFDIR/resolvconf" ]; then 41 SYSCONFDIR="$SYSCONFDIR/resolvconf" 42 if [ -f "$SYSCONFDIR"/interface-order ]; then 43 interface_order="$(cat "$SYSCONFDIR"/interface-order)" 44 fi 45fi 46IFACEDIR="$VARDIR/interfaces" 47METRICDIR="$VARDIR/metrics" 48PRIVATEDIR="$VARDIR/private" 49 50: ${dynamic_order:=tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*} 51: ${interface_order:=lo lo[0-9]*} 52: ${name_server_blacklist:=0.0.0.0} 53 54error_exit() 55{ 56 echo "$*" >&2 57 exit 1 58} 59 60usage() 61{ 62 cat <<-EOF 63 Usage: ${RESOLVCONF##*/} [options] 64 65 Inform the system about any DNS updates. 66 67 Options: 68 -a \$INTERFACE Add DNS information to the specified interface 69 (DNS supplied via stdin in resolv.conf format) 70 -m metric Give the added DNS information a metric 71 -p Mark the interface as private 72 -d \$INTERFACE Delete DNS information from the specified interface 73 -f Ignore non existant interfaces 74 -I Init the state dir 75 -u Run updates from our current DNS information 76 -l [\$PATTERN] Show DNS information, optionally from interfaces 77 that match the specified pattern 78 -i [\$PATTERN] Show interfaces that have supplied DNS information 79 optionally from interfaces that match the specified 80 pattern 81 -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to 82 the console 83 -h Show this help cruft 84 EOF 85 [ -z "$1" ] && exit 0 86 echo 87 error_exit "$*" 88} 89 90echo_resolv() 91{ 92 local line= OIFS="$IFS" 93 94 [ -n "$1" -a -e "$IFACEDIR/$1" ] || return 1 95 echo "# resolv.conf from $1" 96 # Our variable maker works of the fact each resolv.conf per interface 97 # is separated by blank lines. 98 # So we remove them when echoing them. 99 while read -r line; do 100 IFS="$OIFS" 101 if [ -n "$line" ]; then 102 # We need to set IFS here to preserve any whitespace 103 IFS='' 104 printf "%s\n" "$line" 105 fi 106 done < "$IFACEDIR/$1" 107 echo 108 IFS="$OIFS" 109} 110 111# Parse resolv.conf's and make variables 112# for domain name servers, search name servers and global nameservers 113parse_resolv() 114{ 115 local line= ns= ds= search= d= n= newns= 116 local new=true iface= private=false p= domain= 117 118 newns= 119 120 while read -r line; do 121 case "$line" in 122 "# resolv.conf from "*) 123 if ${new}; then 124 iface="${line#\# resolv.conf from *}" 125 new=false 126 if [ -e "$PRIVATEDIR/$iface" ]; then 127 private=true 128 else 129 # Allow expansion 130 cd "$IFACEDIR" 131 private=false 132 for p in $private_interfaces; do 133 case "$iface" in 134 "$p"|"$p":*) private=true; break;; 135 esac 136 done 137 fi 138 fi 139 ;; 140 "nameserver "*) 141 case "${line#* }" in 142 127.*|0.0.0.0|255.255.255.255|::1) 143 echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\"" 144 continue 145 ;; 146 esac 147 ns="$ns${line#* } " 148 ;; 149 "domain "*) 150 if [ -z "$domain" ]; then 151 domain="${line#* }" 152 echo "DOMAIN=\"$domain\"" 153 fi 154 search="${line#* }" 155 ;; 156 "search "*) 157 search="${line#* }" 158 ;; 159 *) 160 [ -n "$line" ] && continue 161 if [ -n "$ns" -a -n "$search" ]; then 162 newns= 163 for n in $ns; do 164 newns="$newns${newns:+,}$n" 165 done 166 ds= 167 for d in $search; do 168 ds="$ds${ds:+ }$d:$newns" 169 done 170 echo "DOMAINS=\"\$DOMAINS $ds\"" 171 fi 172 echo "SEARCH=\"\$SEARCH $search\"" 173 if ! $private; then 174 echo "NAMESERVERS=\"\$NAMESERVERS $ns\"" 175 fi 176 ns= 177 search= 178 new=true 179 ;; 180 esac 181 done 182} 183 184uniqify() 185{ 186 local result= 187 while [ -n "$1" ]; do 188 case " $result " in 189 *" $1 "*);; 190 *) result="$result $1";; 191 esac 192 shift 193 done 194 echo "${result# *}" 195} 196 197dirname() 198{ 199 local dir= OIFS="$IFS" 200 local IFS=/ 201 set -- $@ 202 IFS="$OIFS" 203 if [ -n "$1" ]; then 204 printf %s . 205 else 206 shift 207 fi 208 while [ -n "$2" ]; do 209 printf "/%s" "$1" 210 shift 211 done 212 printf "\n" 213} 214 215config_mkdirs() 216{ 217 local e=0 f d 218 for f; do 219 [ -n "$f" ] || continue 220 d="$(dirname "$f")" 221 if [ ! -d "$d" ]; then 222 if type install >/dev/null 2>&1; then 223 install -d "$d" || e=$? 224 else 225 mkdir "$d" || e=$? 226 fi 227 fi 228 done 229 return $e 230} 231 232list_resolv() 233{ 234 [ -d "$IFACEDIR" ] || return 0 235 236 local report=false list= retval=0 cmd="$1" 237 shift 238 239 # If we have an interface ordering list, then use that. 240 # It works by just using pathname expansion in the interface directory. 241 if [ -n "$1" ]; then 242 list="$*" 243 $force || report=true 244 else 245 cd "$IFACEDIR" 246 for i in $interface_order; do 247 [ -e "$i" ] && list="$list $i" 248 for ii in "$i":*; do 249 [ -e "$ii" ] && list="$list $ii" 250 done 251 done 252 for i in $dynamic_order; do 253 if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then 254 list="$list $i" 255 fi 256 for ii in "$i":*; do 257 if [ -e "$ii" -a ! -e "$METRICDIR/"*" $ii" ]; then 258 list="$list $ii" 259 fi 260 done 261 done 262 if [ -d "$METRICDIR" ]; then 263 cd "$METRICDIR" 264 for i in *; do 265 list="$list ${i#* }" 266 done 267 fi 268 list="$list *" 269 fi 270 271 cd "$IFACEDIR" 272 for i in $(uniqify $list); do 273 # Only list interfaces which we really have 274 if ! [ -e "$i" ]; then 275 if $report; then 276 echo "No resolv.conf for interface $i" >&2 277 retval=$(($retval + 1)) 278 fi 279 continue 280 fi 281 282 if [ "$cmd" = i -o "$cmd" = "-i" ]; then 283 printf %s "$i " 284 else 285 echo_resolv "$i" 286 fi 287 done 288 [ "$cmd" = i -o "$cmd" = "-i" ] && echo 289 return $retval 290} 291 292list_remove() { 293 local list= e= l= result= found= retval=0 294 295 [ -z "$2" ] && return 0 296 eval list=\"\$$1\" 297 shift 298 299 set -f 300 for e; do 301 found=false 302 for l in $list; do 303 case "$e" in 304 $l) found=true;; 305 esac 306 $found && break 307 done 308 if $found; then 309 retval=$(($retval + 1)) 310 else 311 result="$result $e" 312 fi 313 done 314 set +f 315 echo "${result# *}" 316 return $retval 317} 318 319echo_prepend() 320{ 321 echo "# Generated by resolvconf" 322 if [ -n "$search_domains" ]; then 323 echo "search $search_domains" 324 fi 325 for n in $name_servers; do 326 echo "nameserver $n" 327 done 328 echo 329} 330 331echo_append() 332{ 333 echo "# Generated by resolvconf" 334 if [ -n "$search_domains_append" ]; then 335 echo "search $search_domains_append" 336 fi 337 for n in $name_servers_append; do 338 echo "nameserver $n" 339 done 340 echo 341} 342 343make_vars() 344{ 345 local newdomains= d= dn= newns= ns= 346 347 # Clear variables 348 DOMAIN= 349 DOMAINS= 350 SEARCH= 351 NAMESERVERS= 352 LOCALNAMESERVERS= 353 354 if [ -n "$name_servers" -o -n "$search_domains" ]; then 355 eval "$(echo_prepend | parse_resolv)" 356 fi 357 eval "$(list_resolv -l "$@" | parse_resolv)" 358 if [ -n "$name_servers_append" -o -n "$search_domains_append" ]; then 359 eval "$(echo_append | parse_resolv)" 360 fi 361 362 # Ensure that we only list each domain once 363 for d in $DOMAINS; do 364 dn="${d%%:*}" 365 list_remove domain_blacklist "$dn" >/dev/null || continue 366 case " $newdomains" in 367 *" ${dn}:"*) continue;; 368 esac 369 newns= 370 for nd in $DOMAINS; do 371 if [ "$dn" = "${nd%%:*}" ]; then 372 ns="${nd#*:}" 373 while [ -n "$ns" ]; do 374 case ",$newns," in 375 *,${ns%%,*},*) ;; 376 *) list_remove name_server_blacklist \ 377 "$ns" >/dev/null \ 378 && newns="$newns${newns:+,}${ns%%,*}";; 379 esac 380 [ "$ns" = "${ns#*,}" ] && break 381 ns="${ns#*,}" 382 done 383 fi 384 done 385 if [ -n "$newns" ]; then 386 newdomains="$newdomains${newdomains:+ }$dn:$newns" 387 fi 388 done 389 DOMAIN="$(list_remove domain_blacklist $DOMAIN)" 390 SEARCH="$(uniqify $SEARCH)" 391 SEARCH="$(list_remove domain_blacklist $SEARCH)" 392 NAMESERVERS="$(uniqify $NAMESERVERS)" 393 NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)" 394 LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)" 395 LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)" 396 echo "DOMAIN='$DOMAIN'" 397 echo "SEARCH='$SEARCH'" 398 echo "NAMESERVERS='$NAMESERVERS'" 399 echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'" 400 echo "DOMAINS='$newdomains'" 401} 402 403force=false 404while getopts a:Dd:fhIilm:puv OPT; do 405 case "$OPT" in 406 f) force=true;; 407 h) usage;; 408 m) IF_METRIC="$OPTARG";; 409 p) IF_PRIVATE=1;; 410 '?') ;; 411 *) cmd="$OPT"; iface="$OPTARG";; 412 esac 413done 414shift $(($OPTIND - 1)) 415args="$iface${iface:+ }$*" 416 417# -I inits the state dir 418if [ "$cmd" = I ]; then 419 if [ -d "$VARDIR" ]; then 420 rm -rf "$VARDIR"/* 421 fi 422 exit $? 423fi 424 425# -D ensures that the listed config file base dirs exist 426if [ "$cmd" = D ]; then 427 config_mkdirs "$@" 428 exit $? 429fi 430 431# -l lists our resolv files, optionally for a specific interface 432if [ "$cmd" = l -o "$cmd" = i ]; then 433 list_resolv "$cmd" "$args" 434 exit $? 435fi 436 437# Not normally needed, but subscribers should be able to run independently 438if [ "$cmd" = v ]; then 439 make_vars "$iface" 440 exit $? 441fi 442 443# Test that we have valid options 444if [ "$cmd" = a -o "$cmd" = d ]; then 445 if [ -z "$iface" ]; then 446 usage "Interface not specified" 447 fi 448elif [ "$cmd" != u ]; then 449 [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd" 450 usage 451fi 452if [ "$cmd" = a ]; then 453 for x in '/' \\ ' ' '*'; do 454 case "$iface" in 455 *[$x]*) error_exit "$x not allowed in interface name";; 456 esac 457 done 458 for x in '.' '-' '~'; do 459 case "$iface" in 460 [$x]*) error_exit \ 461 "$x not allowed at start of interface name";; 462 esac 463 done 464 [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin" 465fi 466 467if [ ! -d "$IFACEDIR" ]; then 468 if [ ! -d "$VARDIR" ]; then 469 if [ -L "$VARDIR" ]; then 470 dir="$(readlink "$VARDIR")" 471 # link maybe relative 472 cd "${VARDIR%/*}" 473 if ! mkdir -m 0755 -p "$dir"; then 474 error_exit "Failed to create needed" \ 475 "directory $dir" 476 fi 477 else 478 if ! mkdir -m 0755 -p "$VARDIR"; then 479 error_exit "Failed to create needed" \ 480 "directory $VARDIR" 481 fi 482 fi 483 fi 484 mkdir -m 0755 -p "$IFACEDIR" || \ 485 error_exit "Failed to create needed directory $IFACEDIR" 486else 487 # Delete any existing information about the interface 488 if [ "$cmd" = d ]; then 489 cd "$IFACEDIR" 490 for i in $args; do 491 if [ "$cmd" = d -a ! -e "$i" ]; then 492 $force && continue 493 error_exit "No resolv.conf for" \ 494 "interface $i" 495 fi 496 rm -f "$i" "$METRICDIR/"*" $i" \ 497 "$PRIVATEDIR/$i" || exit $? 498 done 499 fi 500fi 501 502if [ "$cmd" = a ]; then 503 # Read resolv.conf from stdin 504 resolv="$(cat)" 505 changed=false 506 # If what we are given matches what we have, then do nothing 507 if [ -e "$IFACEDIR/$iface" ]; then 508 if [ "$(echo "$resolv")" != \ 509 "$(cat "$IFACEDIR/$iface")" ] 510 then 511 rm "$IFACEDIR/$iface" 512 changed=true 513 fi 514 else 515 changed=true 516 fi 517 if $changed; then 518 echo "$resolv" >"$IFACEDIR/$iface" || exit $? 519 fi 520 [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR" 521 oldmetric="$METRICDIR/"*" $iface" 522 newmetric= 523 if [ -n "$IF_METRIC" ]; then 524 # Pad metric to 6 characters, so 5 is less than 10 525 while [ ${#IF_METRIC} -le 6 ]; do 526 IF_METRIC="0$IF_METRIC" 527 done 528 newmetric="$METRICDIR/$IF_METRIC $iface" 529 fi 530 rm -f "$METRICDIR/"*" $iface" 531 [ "$oldmetric" != "$newmetric" -a \ 532 "$oldmetric" != "$METRICDIR/* $iface" ] && 533 changed=true 534 [ -n "$newmetric" ] && echo " " >"$newmetric" 535 case "$IF_PRIVATE" in 536 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) 537 if [ ! -d "$PRIVATEDIR" ]; then 538 [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR" 539 mkdir "$PRIVATEDIR" 540 fi 541 [ -e "$PRIVATEDIR/$iface" ] || changed=true 542 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface" 543 ;; 544 *) 545 if [ -e "$PRIVATEDIR/$iface" ]; then 546 rm -f "$PRIVATEDIR/$iface" 547 changed=true 548 fi 549 ;; 550 esac 551 $changed || exit 0 552 unset changed oldmetric newmetric 553fi 554 555eval "$(make_vars)" 556export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS 557: ${list_resolv:=list_resolv -l} 558retval=0 559for script in "$LIBEXECDIR"/*; do 560 if [ -f "$script" ]; then 561 if [ -x "$script" ]; then 562 "$script" "$cmd" "$iface" 563 else 564 (set -- "$cmd" "$iface"; . "$script") 565 fi 566 retval=$(($retval + $?)) 567 fi 568done 569exit $retval 570