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=" 108 echo "NAMESERVERS=" 109 110 while read line; do 111 case "$line" in 112 "# resolv.conf from "*) 113 if ${new}; then 114 iface="${line#\# resolv.conf from *}" 115 new=false 116 if [ -e "$PRIVATEDIR/$iface" ]; then 117 private=true 118 else 119 # Allow expansion 120 cd "$IFACEDIR" 121 private=false 122 for p in $private_interfaces; do 123 if [ "$p" = "$iface" ]; then 124 private=true 125 break 126 fi 127 done 128 fi 129 fi 130 ;; 131 "nameserver "*) 132 case "${line#* }" in 133 127.*|0.0.0.0|255.255.255.255) continue;; 134 esac 135 ns="$ns${line#* } " 136 ;; 137 "domain "*|"search "*) 138 search="${line#* }" 139 ;; 140 *) 141 [ -n "$line" ] && continue 142 if [ -n "$ns" -a -n "$search" ]; then 143 newns= 144 for n in $ns; do 145 newns="$newns${newns:+,}$n" 146 done 147 ds= 148 for d in $search; do 149 ds="$ds${ds:+ }$d:$newns" 150 done 151 echo "DOMAINS=\"\$DOMAINS $ds\"" 152 fi 153 echo "SEARCH=\"\$SEARCH $search\"" 154 if ! $private; then 155 echo "NAMESERVERS=\"\$NAMESERVERS $ns\"" 156 fi 157 ns= 158 search= 159 new=true 160 ;; 161 esac 162 done 163} 164 165uniqify() 166{ 167 local result= 168 while [ -n "$1" ]; do 169 case " $result " in 170 *" $1 "*);; 171 *) result="$result $1";; 172 esac 173 shift 174 done 175 echo "${result# *}" 176} 177 178list_resolv() 179{ 180 [ -d "$IFACEDIR" ] || return 0 181 182 local report=false list= retval=0 cmd="$1" 183 shift 184 185 # If we have an interface ordering list, then use that. 186 # It works by just using pathname expansion in the interface directory. 187 if [ -n "$1" ]; then 188 list="$@" 189 $force || report=true 190 else 191 cd "$IFACEDIR" 192 for i in $interface_order; do 193 [ -e "$i" ] && list="$list $i" 194 done 195 for i in $dynamic_order; do 196 if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then 197 list="$list $i" 198 fi 199 done 200 if [ -d "$METRICDIR" ]; then 201 cd "$METRICDIR" 202 for i in *; do 203 list="$list ${i#* }" 204 done 205 fi 206 list="$list *" 207 fi 208 209 cd "$IFACEDIR" 210 for i in $(uniqify $list); do 211 # Only list interfaces which we really have 212 if ! [ -e "$i" ]; then 213 if $report; then 214 echo "No resolv.conf for interface $i" >&2 215 retval=$(($retval + 1)) 216 fi 217 continue 218 fi 219 220 if [ "$cmd" = i -o "$cmd" = "-i" ]; then 221 printf "$i " 222 else 223 echo_resolv "$i" 224 fi 225 done 226 [ "$cmd" = i -o "$cmd" = "-i" ] && echo 227 return $retval 228} 229 230make_vars() 231{ 232 eval "$(list_resolv -l "$@" | parse_resolv)" 233 234 # Ensure that we only list each domain once 235 newdomains= 236 for d in $DOMAINS; do 237 dn="${d%%:*}" 238 case " $newdomains" in 239 *" ${dn}:"*) continue;; 240 esac 241 newdomains="$newdomains${newdomains:+ }$dn:" 242 newns= 243 for nd in $DOMAINS; do 244 if [ "$dn" = "${nd%%:*}" ]; then 245 ns="${nd#*:}" 246 while [ -n "$ns" ]; do 247 case ",$newns," in 248 *,${ns%%,*},*) ;; 249 *) newns="$newns${newns:+,}${ns%%,*}";; 250 esac 251 [ "$ns" = "${ns#*,}" ] && break 252 ns="${ns#*,}" 253 done 254 fi 255 done 256 newdomains="$newdomains$newns" 257 done 258 echo "DOMAINS='$newdomains'" 259 echo "SEARCH='$(uniqify $SEARCH)'" 260 echo "NAMESERVERS='$(uniqify $NAMESERVERS)'" 261} 262 263force=false 264while getopts a:d:fhilm:puv OPT; do 265 case "$OPT" in 266 f) force=true;; 267 h) usage;; 268 m) IF_METRIC="$OPTARG";; 269 p) IF_PRIVATE=1;; 270 '?') ;; 271 *) cmd="$OPT"; iface="$OPTARG";; 272 esac 273done 274shift $(($OPTIND - 1)) 275args="$iface${iface:+ }$@" 276 277# -I inits the state dir 278if [ "$cmd" = I ]; then 279 if [ -d "$VARDIR" ]; then 280 rm -rf "$VARDIR"/* 281 fi 282 exit $? 283fi 284 285# -l lists our resolv files, optionally for a specific interface 286if [ "$cmd" = l -o "$cmd" = i ]; then 287 list_resolv "$cmd" "$args" 288 exit $? 289fi 290 291# Not normally needed, but subscribers should be able to run independently 292if [ "$cmd" = v ]; then 293 make_vars "$iface" 294 exit $? 295fi 296 297# Test that we have valid options 298if [ "$cmd" = a -o "$cmd" = d ]; then 299 if [ -z "$iface" ]; then 300 usage "Interface not specified" 301 fi 302elif [ "$cmd" != u ]; then 303 [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd" 304 usage 305fi 306if [ "$cmd" = a ]; then 307 for x in '/' \\ ' ' '*'; do 308 case "$iface" in 309 *[$x]*) error_exit "$x not allowed in interface name";; 310 esac 311 done 312 for x in '.' '-' '~'; do 313 case "$iface" in 314 [$x]*) error_exit \ 315 "$x not allowed at start of interface name";; 316 esac 317 done 318 [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin" 319fi 320 321if [ ! -d "$IFACEDIR" ]; then 322 if [ ! -d "$VARDIR" ]; then 323 if [ -L "$VARDIR" ]; then 324 dir="$(readlink "$VARDIR")" 325 # link maybe relative 326 cd "${VARDIR%/*}" 327 if ! mkdir -m 0755 -p "$dir"; then 328 error_exit "Failed to create needed" \ 329 "directory $dir" 330 fi 331 else 332 if ! mkdir -m 0755 -p "$VARDIR"; then 333 error_exit "Failed to create needed" \ 334 "directory $VARDIR" 335 fi 336 fi 337 fi 338 mkdir -m 0755 -p "$IFACEDIR" || \ 339 error_exit "Failed to create needed directory $IFACEDIR" 340else 341 # Delete any existing information about the interface 342 if [ "$cmd" = d ]; then 343 cd "$IFACEDIR" 344 for i in $args; do 345 if [ "$cmd" = d -a ! -e "$i" ]; then 346 $force && continue 347 error_exit "No resolv.conf for" \ 348 "interface $i" 349 fi 350 rm -f "$i" "$METRICDIR/"*" $i" \ 351 "$PRIVATEDIR/$i" || exit $? 352 done 353 fi 354fi 355 356if [ "$cmd" = a ]; then 357 # Read resolv.conf from stdin 358 resolv="$(cat)\n" 359 # If what we are given matches what we have, then do nothing 360 if [ -e "$IFACEDIR/$iface" ]; then 361 if [ "$(printf "$resolv")" = \ 362 "$(cat "$IFACEDIR/$iface")" ] 363 then 364 exit 0 365 fi 366 rm "$IFACEDIR/$iface" 367 fi 368 printf "$resolv" >"$IFACEDIR/$iface" || exit $? 369 [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR" 370 rm -f "$METRICDIR/"*" $iface" 371 if [ -n "$IF_METRIC" ]; then 372 # Pad metric to 6 characters, so 5 is less than 10 373 while [ ${#IF_METRIC} -le 6 ]; do 374 IF_METRIC="0$IF_METRIC" 375 done 376 echo " " >"$METRICDIR/$IF_METRIC $iface" 377 fi 378 case "$IF_PRIVATE" in 379 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) 380 if [ ! -d "$PRIVATEDIR" ]; then 381 [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR" 382 mkdir "$PRIVATEDIR" 383 fi 384 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface" 385 ;; 386 *) 387 if [ -e "$PRIVATEDIR/$iface" ]; then 388 rm -f "$PRIVATEDIR/$iface" 389 fi 390 ;; 391 esac 392fi 393 394eval "$(make_vars)" 395export RESOLVCONF DOMAINS SEARCH NAMESERVERS 396: ${list_resolv:=list_resolv -l} 397retval=0 398for script in "$LIBEXECDIR"/*; do 399 if [ -f "$script" ]; then 400 if [ -x "$script" ]; then 401 "$script" "$cmd" "$iface" 402 else 403 (. "$script" "$cmd" "$fiace") 404 fi 405 retval=$(($retval + $?)) 406 fi 407done 408exit $retval 409