1#!/bin/sh 2# Copyright (c) 2007-2009 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# Support original resolvconf configuration layout 32# as well as the openresolv config file 33if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then 34 . "$SYSCONFDIR"/resolvconf.conf 35 [ -n "$state_dir" ] && VARDIR="$state_dir" 36elif [ -d "$SYSCONFDIR/resolvconf" ]; then 37 SYSCONFDIR="$SYSCONFDIR/resolvconf" 38 if [ -f "$SYSCONFDIR"/interface-order ]; then 39 interface_order="$(cat "$SYSCONFDIR"/interface-order)" 40 fi 41fi 42IFACEDIR="$VARDIR/interfaces" 43METRICDIR="$VARDIR/metrics" 44PRIVATEDIR="$VARDIR/private" 45 46: ${dynamic_order:=tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*} 47: ${interface_order:=lo lo[0-9]*} 48 49error_exit() 50{ 51 echo "$*" >&2 52 exit 1 53} 54 55usage() 56{ 57 cat <<-EOF 58 Usage: ${RESOLVCONF##*/} [options] 59 60 Inform the system about any DNS updates. 61 62 Options: 63 -a \$INTERFACE Add DNS information to the specified interface 64 (DNS supplied via stdin in resolv.conf format) 65 -m metric Give the added DNS information a metric 66 -p Mark the interface as private 67 -d \$INTERFACE Delete DNS information from the specified interface 68 -f Ignore non existant interfaces 69 -I Init the state dir 70 -u Run updates from our current DNS information 71 -l [\$PATTERN] Show DNS information, optionally from interfaces 72 that match the specified pattern 73 -i [\$PATTERN] Show interfaces that have supplied DNS information 74 optionally from interfaces that match the specified 75 pattern 76 -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to 77 the console 78 -h Show this help cruft 79 EOF 80 [ -z "$1" ] && exit 0 81 echo 82 error_exit "$*" 83} 84 85echo_resolv() 86{ 87 local line= 88 [ -n "$1" -a -e "$IFACEDIR/$1" ] || return 1 89 echo "# resolv.conf from $1" 90 # Our variable maker works of the fact each resolv.conf per interface 91 # is separated by blank lines. 92 # So we remove them when echoing them. 93 while read line; do 94 [ -n "$line" ] && echo "$line" 95 done < "$IFACEDIR/$1" 96 echo 97} 98 99# Parse resolv.conf's and make variables 100# for domain name servers, search name servers and global nameservers 101parse_resolv() 102{ 103 local line= ns= ds= search= d= n= newns= 104 local new=true iface= private=false p= 105 106 echo "DOMAINS=" 107 echo "SEARCH=\"$search_domains\"" 108 # let our subscribers know about global nameservers 109 for n in $name_servers; do 110 case "$n" in 111 127.*|0.0.0.0|255.255.255.255|::1) :;; 112 *) newns="$newns${newns:+ }$n";; 113 esac 114 done 115 echo "NAMESERVERS=\"$newns\"" 116 echo "LOCALNAMESERVERS=" 117 newns= 118 119 while read line; do 120 case "$line" in 121 "# resolv.conf from "*) 122 if ${new}; then 123 iface="${line#\# resolv.conf from *}" 124 new=false 125 if [ -e "$PRIVATEDIR/$iface" ]; then 126 private=true 127 else 128 # Allow expansion 129 cd "$IFACEDIR" 130 private=false 131 for p in $private_interfaces; do 132 if [ "$p" = "$iface" ]; then 133 private=true 134 break 135 fi 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 "*|"search "*) 150 search="${line#* }" 151 ;; 152 *) 153 [ -n "$line" ] && continue 154 if [ -n "$ns" -a -n "$search" ]; then 155 newns= 156 for n in $ns; do 157 newns="$newns${newns:+,}$n" 158 done 159 ds= 160 for d in $search; do 161 ds="$ds${ds:+ }$d:$newns" 162 done 163 echo "DOMAINS=\"\$DOMAINS $ds\"" 164 fi 165 echo "SEARCH=\"\$SEARCH $search\"" 166 if ! $private; then 167 echo "NAMESERVERS=\"\$NAMESERVERS $ns\"" 168 fi 169 ns= 170 search= 171 new=true 172 ;; 173 esac 174 done 175} 176 177uniqify() 178{ 179 local result= 180 while [ -n "$1" ]; do 181 case " $result " in 182 *" $1 "*);; 183 *) result="$result $1";; 184 esac 185 shift 186 done 187 echo "${result# *}" 188} 189 190list_resolv() 191{ 192 [ -d "$IFACEDIR" ] || return 0 193 194 local report=false list= retval=0 cmd="$1" 195 shift 196 197 # If we have an interface ordering list, then use that. 198 # It works by just using pathname expansion in the interface directory. 199 if [ -n "$1" ]; then 200 list="$@" 201 $force || report=true 202 else 203 cd "$IFACEDIR" 204 for i in $interface_order; do 205 [ -e "$i" ] && list="$list $i" 206 done 207 for i in $dynamic_order; do 208 if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then 209 list="$list $i" 210 fi 211 done 212 if [ -d "$METRICDIR" ]; then 213 cd "$METRICDIR" 214 for i in *; do 215 list="$list ${i#* }" 216 done 217 fi 218 list="$list *" 219 fi 220 221 cd "$IFACEDIR" 222 for i in $(uniqify $list); do 223 # Only list interfaces which we really have 224 if ! [ -e "$i" ]; then 225 if $report; then 226 echo "No resolv.conf for interface $i" >&2 227 retval=$(($retval + 1)) 228 fi 229 continue 230 fi 231 232 if [ "$cmd" = i -o "$cmd" = "-i" ]; then 233 printf "$i " 234 else 235 echo_resolv "$i" 236 fi 237 done 238 [ "$cmd" = i -o "$cmd" = "-i" ] && echo 239 return $retval 240} 241 242make_vars() 243{ 244 eval "$(list_resolv -l "$@" | parse_resolv)" 245 246 # Ensure that we only list each domain once 247 newdomains= 248 for d in $DOMAINS; do 249 dn="${d%%:*}" 250 case " $newdomains" in 251 *" ${dn}:"*) continue;; 252 esac 253 newdomains="$newdomains${newdomains:+ }$dn:" 254 newns= 255 for nd in $DOMAINS; do 256 if [ "$dn" = "${nd%%:*}" ]; then 257 ns="${nd#*:}" 258 while [ -n "$ns" ]; do 259 case ",$newns," in 260 *,${ns%%,*},*) ;; 261 *) newns="$newns${newns:+,}${ns%%,*}";; 262 esac 263 [ "$ns" = "${ns#*,}" ] && break 264 ns="${ns#*,}" 265 done 266 fi 267 done 268 newdomains="$newdomains$newns" 269 done 270 echo "DOMAINS='$newdomains'" 271 echo "SEARCH='$(uniqify $SEARCH)'" 272 echo "NAMESERVERS='$(uniqify $NAMESERVERS)'" 273 echo "LOCALNAMESERVERS='$(uniqify $LOCALNAMESERVERS)'" 274} 275 276force=false 277while getopts a:d:fhIilm:puv OPT; do 278 case "$OPT" in 279 f) force=true;; 280 h) usage;; 281 m) IF_METRIC="$OPTARG";; 282 p) IF_PRIVATE=1;; 283 '?') ;; 284 *) cmd="$OPT"; iface="$OPTARG";; 285 esac 286done 287shift $(($OPTIND - 1)) 288args="$iface${iface:+ }$@" 289 290# -I inits the state dir 291if [ "$cmd" = I ]; then 292 if [ -d "$VARDIR" ]; then 293 rm -rf "$VARDIR"/* 294 fi 295 exit $? 296fi 297 298# -l lists our resolv files, optionally for a specific interface 299if [ "$cmd" = l -o "$cmd" = i ]; then 300 list_resolv "$cmd" "$args" 301 exit $? 302fi 303 304# Not normally needed, but subscribers should be able to run independently 305if [ "$cmd" = v ]; then 306 make_vars "$iface" 307 exit $? 308fi 309 310# Test that we have valid options 311if [ "$cmd" = a -o "$cmd" = d ]; then 312 if [ -z "$iface" ]; then 313 usage "Interface not specified" 314 fi 315elif [ "$cmd" != u ]; then 316 [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd" 317 usage 318fi 319if [ "$cmd" = a ]; then 320 for x in '/' \\ ' ' '*'; do 321 case "$iface" in 322 *[$x]*) error_exit "$x not allowed in interface name";; 323 esac 324 done 325 for x in '.' '-' '~'; do 326 case "$iface" in 327 [$x]*) error_exit \ 328 "$x not allowed at start of interface name";; 329 esac 330 done 331 [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin" 332fi 333 334if [ ! -d "$IFACEDIR" ]; then 335 if [ ! -d "$VARDIR" ]; then 336 if [ -L "$VARDIR" ]; then 337 dir="$(readlink "$VARDIR")" 338 # link maybe relative 339 cd "${VARDIR%/*}" 340 if ! mkdir -m 0755 -p "$dir"; then 341 error_exit "Failed to create needed" \ 342 "directory $dir" 343 fi 344 else 345 if ! mkdir -m 0755 -p "$VARDIR"; then 346 error_exit "Failed to create needed" \ 347 "directory $VARDIR" 348 fi 349 fi 350 fi 351 mkdir -m 0755 -p "$IFACEDIR" || \ 352 error_exit "Failed to create needed directory $IFACEDIR" 353else 354 # Delete any existing information about the interface 355 if [ "$cmd" = d ]; then 356 cd "$IFACEDIR" 357 for i in $args; do 358 if [ "$cmd" = d -a ! -e "$i" ]; then 359 $force && continue 360 error_exit "No resolv.conf for" \ 361 "interface $i" 362 fi 363 rm -f "$i" "$METRICDIR/"*" $i" \ 364 "$PRIVATEDIR/$i" || exit $? 365 done 366 fi 367fi 368 369if [ "$cmd" = a ]; then 370 # Read resolv.conf from stdin 371 resolv="$(cat)" 372 # If what we are given matches what we have, then do nothing 373 if [ -e "$IFACEDIR/$iface" ]; then 374 if [ "$(echo "$resolv")" = \ 375 "$(cat "$IFACEDIR/$iface")" ] 376 then 377 exit 0 378 fi 379 rm "$IFACEDIR/$iface" 380 fi 381 echo "$resolv" >"$IFACEDIR/$iface" || exit $? 382 [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR" 383 rm -f "$METRICDIR/"*" $iface" 384 if [ -n "$IF_METRIC" ]; then 385 # Pad metric to 6 characters, so 5 is less than 10 386 while [ ${#IF_METRIC} -le 6 ]; do 387 IF_METRIC="0$IF_METRIC" 388 done 389 echo " " >"$METRICDIR/$IF_METRIC $iface" 390 fi 391 case "$IF_PRIVATE" in 392 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) 393 if [ ! -d "$PRIVATEDIR" ]; then 394 [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR" 395 mkdir "$PRIVATEDIR" 396 fi 397 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface" 398 ;; 399 *) 400 if [ -e "$PRIVATEDIR/$iface" ]; then 401 rm -f "$PRIVATEDIR/$iface" 402 fi 403 ;; 404 esac 405fi 406 407eval "$(make_vars)" 408export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS 409: ${list_resolv:=list_resolv -l} 410retval=0 411for script in "$LIBEXECDIR"/*; do 412 if [ -f "$script" ]; then 413 if [ -x "$script" ]; then 414 "$script" "$cmd" "$iface" 415 else 416 (. "$script" "$cmd" "$iface") 417 fi 418 retval=$(($retval + $?)) 419 fi 420done 421exit $retval 422