1219732Sume#!/bin/sh 2219732Sume# Copyright (c) 2007-2009 Roy Marples 3219732Sume# All rights reserved 4219732Sume 5219732Sume# Redistribution and use in source and binary forms, with or without 6219732Sume# modification, are permitted provided that the following conditions 7219732Sume# are met: 8219732Sume# * Redistributions of source code must retain the above copyright 9219732Sume# notice, this list of conditions and the following disclaimer. 10219732Sume# * Redistributions in binary form must reproduce the above 11219732Sume# copyright notice, this list of conditions and the following 12219732Sume# disclaimer in the documentation and/or other materials provided 13219732Sume# with the distribution. 14219732Sume# 15219732Sume# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16219732Sume# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17219732Sume# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18219732Sume# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19219732Sume# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20219732Sume# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21219732Sume# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22219732Sume# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23219732Sume# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24219732Sume# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25219732Sume# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26219732Sume 27219732SumeRESOLVCONF="$0" 28219732SumeSYSCONFDIR=@SYSCONFDIR@ 29219732SumeLIBEXECDIR=@LIBEXECDIR@ 30219732SumeVARDIR=@VARDIR@ 31219732Sume# Support original resolvconf configuration layout 32219732Sume# as well as the openresolv config file 33219732Sumeif [ -f "$SYSCONFDIR"/resolvconf.conf ]; then 34219732Sume . "$SYSCONFDIR"/resolvconf.conf 35219732Sume [ -n "$state_dir" ] && VARDIR="$state_dir" 36219732Sumeelif [ -d "$SYSCONFDIR/resolvconf" ]; then 37219732Sume SYSCONFDIR="$SYSCONFDIR/resolvconf" 38219732Sume if [ -f "$SYSCONFDIR"/interface-order ]; then 39219732Sume interface_order="$(cat "$SYSCONFDIR"/interface-order)" 40219732Sume fi 41219732Sumefi 42219732SumeIFACEDIR="$VARDIR/interfaces" 43219732SumeMETRICDIR="$VARDIR/metrics" 44219732SumePRIVATEDIR="$VARDIR/private" 45219732Sume 46219732Sume: ${dynamic_order:=tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*} 47219732Sume: ${interface_order:=lo lo[0-9]*} 48219732Sume 49219732Sumeerror_exit() 50219732Sume{ 51219732Sume echo "$*" >&2 52219732Sume exit 1 53219732Sume} 54219732Sume 55219732Sumeusage() 56219732Sume{ 57219732Sume cat <<-EOF 58219732Sume Usage: ${RESOLVCONF##*/} [options] 59219732Sume 60219732Sume Inform the system about any DNS updates. 61219732Sume 62219732Sume Options: 63219732Sume -a \$INTERFACE Add DNS information to the specified interface 64219732Sume (DNS supplied via stdin in resolv.conf format) 65219732Sume -m metric Give the added DNS information a metric 66219732Sume -p Mark the interface as private 67219732Sume -d \$INTERFACE Delete DNS information from the specified interface 68219732Sume -f Ignore non existant interfaces 69219732Sume -I Init the state dir 70219732Sume -u Run updates from our current DNS information 71219732Sume -l [\$PATTERN] Show DNS information, optionally from interfaces 72219732Sume that match the specified pattern 73219732Sume -i [\$PATTERN] Show interfaces that have supplied DNS information 74219732Sume optionally from interfaces that match the specified 75219732Sume pattern 76219732Sume -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to 77219732Sume the console 78219732Sume -h Show this help cruft 79219732Sume EOF 80219732Sume [ -z "$1" ] && exit 0 81219732Sume echo 82219732Sume error_exit "$*" 83219732Sume} 84219732Sume 85219732Sumeecho_resolv() 86219732Sume{ 87219732Sume local line= 88219732Sume [ -n "$1" -a -e "$IFACEDIR/$1" ] || return 1 89219732Sume echo "# resolv.conf from $1" 90219732Sume # Our variable maker works of the fact each resolv.conf per interface 91219732Sume # is separated by blank lines. 92219732Sume # So we remove them when echoing them. 93219732Sume while read line; do 94219732Sume [ -n "$line" ] && echo "$line" 95219732Sume done < "$IFACEDIR/$1" 96219732Sume echo 97219732Sume} 98219732Sume 99219732Sume# Parse resolv.conf's and make variables 100219732Sume# for domain name servers, search name servers and global nameservers 101219732Sumeparse_resolv() 102219732Sume{ 103219732Sume local line= ns= ds= search= d= n= newns= 104219732Sume local new=true iface= private=false p= 105219732Sume 106219732Sume echo "DOMAINS=" 107219732Sume echo "SEARCH=\"$search_domains\"" 108219732Sume # let our subscribers know about global nameservers 109219732Sume for n in $name_servers; do 110219732Sume case "$n" in 111219732Sume 127.*|0.0.0.0|255.255.255.255|::1) :;; 112219732Sume *) newns="$newns${newns:+ }$n";; 113219732Sume esac 114219732Sume done 115219732Sume echo "NAMESERVERS=\"$newns\"" 116219732Sume echo "LOCALNAMESERVERS=" 117219732Sume newns= 118219732Sume 119219732Sume while read line; do 120219732Sume case "$line" in 121219732Sume "# resolv.conf from "*) 122219732Sume if ${new}; then 123219732Sume iface="${line#\# resolv.conf from *}" 124219732Sume new=false 125219732Sume if [ -e "$PRIVATEDIR/$iface" ]; then 126219732Sume private=true 127219732Sume else 128219732Sume # Allow expansion 129219732Sume cd "$IFACEDIR" 130219732Sume private=false 131219732Sume for p in $private_interfaces; do 132219732Sume if [ "$p" = "$iface" ]; then 133219732Sume private=true 134219732Sume break 135219732Sume fi 136219732Sume done 137219732Sume fi 138219732Sume fi 139219732Sume ;; 140219732Sume "nameserver "*) 141219732Sume case "${line#* }" in 142219732Sume 127.*|0.0.0.0|255.255.255.255|::1) 143219732Sume echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\"" 144219732Sume continue 145219732Sume ;; 146219732Sume esac 147219732Sume ns="$ns${line#* } " 148219732Sume ;; 149219732Sume "domain "*|"search "*) 150219732Sume search="${line#* }" 151219732Sume ;; 152219732Sume *) 153219732Sume [ -n "$line" ] && continue 154219732Sume if [ -n "$ns" -a -n "$search" ]; then 155219732Sume newns= 156219732Sume for n in $ns; do 157219732Sume newns="$newns${newns:+,}$n" 158219732Sume done 159219732Sume ds= 160219732Sume for d in $search; do 161219732Sume ds="$ds${ds:+ }$d:$newns" 162219732Sume done 163219732Sume echo "DOMAINS=\"\$DOMAINS $ds\"" 164219732Sume fi 165219732Sume echo "SEARCH=\"\$SEARCH $search\"" 166219732Sume if ! $private; then 167219732Sume echo "NAMESERVERS=\"\$NAMESERVERS $ns\"" 168219732Sume fi 169219732Sume ns= 170219732Sume search= 171219732Sume new=true 172219732Sume ;; 173219732Sume esac 174219732Sume done 175219732Sume} 176219732Sume 177219732Sumeuniqify() 178219732Sume{ 179219732Sume local result= 180219732Sume while [ -n "$1" ]; do 181219732Sume case " $result " in 182219732Sume *" $1 "*);; 183219732Sume *) result="$result $1";; 184219732Sume esac 185219732Sume shift 186219732Sume done 187219732Sume echo "${result# *}" 188219732Sume} 189219732Sume 190219732Sumelist_resolv() 191219732Sume{ 192219732Sume [ -d "$IFACEDIR" ] || return 0 193219732Sume 194219732Sume local report=false list= retval=0 cmd="$1" 195219732Sume shift 196219732Sume 197219732Sume # If we have an interface ordering list, then use that. 198219732Sume # It works by just using pathname expansion in the interface directory. 199219732Sume if [ -n "$1" ]; then 200219732Sume list="$@" 201219732Sume $force || report=true 202219732Sume else 203219732Sume cd "$IFACEDIR" 204219732Sume for i in $interface_order; do 205219732Sume [ -e "$i" ] && list="$list $i" 206219732Sume done 207219732Sume for i in $dynamic_order; do 208219732Sume if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then 209219732Sume list="$list $i" 210219732Sume fi 211219732Sume done 212219732Sume if [ -d "$METRICDIR" ]; then 213219732Sume cd "$METRICDIR" 214219732Sume for i in *; do 215219732Sume list="$list ${i#* }" 216219732Sume done 217219732Sume fi 218219732Sume list="$list *" 219219732Sume fi 220219732Sume 221219732Sume cd "$IFACEDIR" 222219732Sume for i in $(uniqify $list); do 223219732Sume # Only list interfaces which we really have 224219732Sume if ! [ -e "$i" ]; then 225219732Sume if $report; then 226219732Sume echo "No resolv.conf for interface $i" >&2 227219732Sume retval=$(($retval + 1)) 228219732Sume fi 229219732Sume continue 230219732Sume fi 231219732Sume 232219732Sume if [ "$cmd" = i -o "$cmd" = "-i" ]; then 233219732Sume printf "$i " 234219732Sume else 235219732Sume echo_resolv "$i" 236219732Sume fi 237219732Sume done 238219732Sume [ "$cmd" = i -o "$cmd" = "-i" ] && echo 239219732Sume return $retval 240219732Sume} 241219732Sume 242219732Sumemake_vars() 243219732Sume{ 244219732Sume eval "$(list_resolv -l "$@" | parse_resolv)" 245219732Sume 246219732Sume # Ensure that we only list each domain once 247219732Sume newdomains= 248219732Sume for d in $DOMAINS; do 249219732Sume dn="${d%%:*}" 250219732Sume case " $newdomains" in 251219732Sume *" ${dn}:"*) continue;; 252219732Sume esac 253219732Sume newdomains="$newdomains${newdomains:+ }$dn:" 254219732Sume newns= 255219732Sume for nd in $DOMAINS; do 256219732Sume if [ "$dn" = "${nd%%:*}" ]; then 257219732Sume ns="${nd#*:}" 258219732Sume while [ -n "$ns" ]; do 259219732Sume case ",$newns," in 260219732Sume *,${ns%%,*},*) ;; 261219732Sume *) newns="$newns${newns:+,}${ns%%,*}";; 262219732Sume esac 263219732Sume [ "$ns" = "${ns#*,}" ] && break 264219732Sume ns="${ns#*,}" 265219732Sume done 266219732Sume fi 267219732Sume done 268219732Sume newdomains="$newdomains$newns" 269219732Sume done 270219732Sume echo "DOMAINS='$newdomains'" 271219732Sume echo "SEARCH='$(uniqify $SEARCH)'" 272219732Sume echo "NAMESERVERS='$(uniqify $NAMESERVERS)'" 273219732Sume echo "LOCALNAMESERVERS='$(uniqify $LOCALNAMESERVERS)'" 274219732Sume} 275219732Sume 276219732Sumeforce=false 277219732Sumewhile getopts a:d:fhIilm:puv OPT; do 278219732Sume case "$OPT" in 279219732Sume f) force=true;; 280219732Sume h) usage;; 281219732Sume m) IF_METRIC="$OPTARG";; 282219732Sume p) IF_PRIVATE=1;; 283219732Sume '?') ;; 284219732Sume *) cmd="$OPT"; iface="$OPTARG";; 285219732Sume esac 286219732Sumedone 287219732Sumeshift $(($OPTIND - 1)) 288219732Sumeargs="$iface${iface:+ }$@" 289219732Sume 290219732Sume# -I inits the state dir 291219732Sumeif [ "$cmd" = I ]; then 292219732Sume if [ -d "$VARDIR" ]; then 293219732Sume rm -rf "$VARDIR"/* 294219732Sume fi 295219732Sume exit $? 296219732Sumefi 297219732Sume 298219732Sume# -l lists our resolv files, optionally for a specific interface 299219732Sumeif [ "$cmd" = l -o "$cmd" = i ]; then 300219732Sume list_resolv "$cmd" "$args" 301219732Sume exit $? 302219732Sumefi 303219732Sume 304219732Sume# Not normally needed, but subscribers should be able to run independently 305219732Sumeif [ "$cmd" = v ]; then 306219732Sume make_vars "$iface" 307219732Sume exit $? 308219732Sumefi 309219732Sume 310219732Sume# Test that we have valid options 311219732Sumeif [ "$cmd" = a -o "$cmd" = d ]; then 312219732Sume if [ -z "$iface" ]; then 313219732Sume usage "Interface not specified" 314219732Sume fi 315219732Sumeelif [ "$cmd" != u ]; then 316219732Sume [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd" 317219732Sume usage 318219732Sumefi 319219732Sumeif [ "$cmd" = a ]; then 320219732Sume for x in '/' \\ ' ' '*'; do 321219732Sume case "$iface" in 322219732Sume *[$x]*) error_exit "$x not allowed in interface name";; 323219732Sume esac 324219732Sume done 325219732Sume for x in '.' '-' '~'; do 326219732Sume case "$iface" in 327219732Sume [$x]*) error_exit \ 328219732Sume "$x not allowed at start of interface name";; 329219732Sume esac 330219732Sume done 331219732Sume [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin" 332219732Sumefi 333219732Sume 334219732Sumeif [ ! -d "$IFACEDIR" ]; then 335219732Sume if [ ! -d "$VARDIR" ]; then 336219732Sume if [ -L "$VARDIR" ]; then 337219732Sume dir="$(readlink "$VARDIR")" 338219732Sume # link maybe relative 339219732Sume cd "${VARDIR%/*}" 340219732Sume if ! mkdir -m 0755 -p "$dir"; then 341219732Sume error_exit "Failed to create needed" \ 342219732Sume "directory $dir" 343219732Sume fi 344219732Sume else 345219732Sume if ! mkdir -m 0755 -p "$VARDIR"; then 346219732Sume error_exit "Failed to create needed" \ 347219732Sume "directory $VARDIR" 348219732Sume fi 349219732Sume fi 350219732Sume fi 351219732Sume mkdir -m 0755 -p "$IFACEDIR" || \ 352219732Sume error_exit "Failed to create needed directory $IFACEDIR" 353219732Sumeelse 354219732Sume # Delete any existing information about the interface 355219732Sume if [ "$cmd" = d ]; then 356219732Sume cd "$IFACEDIR" 357219732Sume for i in $args; do 358219732Sume if [ "$cmd" = d -a ! -e "$i" ]; then 359219732Sume $force && continue 360219732Sume error_exit "No resolv.conf for" \ 361219732Sume "interface $i" 362219732Sume fi 363219732Sume rm -f "$i" "$METRICDIR/"*" $i" \ 364219732Sume "$PRIVATEDIR/$i" || exit $? 365219732Sume done 366219732Sume fi 367219732Sumefi 368219732Sume 369219732Sumeif [ "$cmd" = a ]; then 370219732Sume # Read resolv.conf from stdin 371225524Shrs resolv="$(cat)" 372219732Sume # If what we are given matches what we have, then do nothing 373219732Sume if [ -e "$IFACEDIR/$iface" ]; then 374225524Shrs if [ "$(echo "$resolv")" = \ 375219732Sume "$(cat "$IFACEDIR/$iface")" ] 376219732Sume then 377219732Sume exit 0 378219732Sume fi 379219732Sume rm "$IFACEDIR/$iface" 380219732Sume fi 381225524Shrs echo "$resolv" >"$IFACEDIR/$iface" || exit $? 382219732Sume [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR" 383219732Sume rm -f "$METRICDIR/"*" $iface" 384219732Sume if [ -n "$IF_METRIC" ]; then 385219732Sume # Pad metric to 6 characters, so 5 is less than 10 386219732Sume while [ ${#IF_METRIC} -le 6 ]; do 387219732Sume IF_METRIC="0$IF_METRIC" 388219732Sume done 389219732Sume echo " " >"$METRICDIR/$IF_METRIC $iface" 390219732Sume fi 391219732Sume case "$IF_PRIVATE" in 392219732Sume [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) 393219732Sume if [ ! -d "$PRIVATEDIR" ]; then 394219732Sume [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR" 395219732Sume mkdir "$PRIVATEDIR" 396219732Sume fi 397219732Sume [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface" 398219732Sume ;; 399219732Sume *) 400219732Sume if [ -e "$PRIVATEDIR/$iface" ]; then 401219732Sume rm -f "$PRIVATEDIR/$iface" 402219732Sume fi 403219732Sume ;; 404219732Sume esac 405219732Sumefi 406219732Sume 407219732Sumeeval "$(make_vars)" 408219732Sumeexport RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS 409219732Sume: ${list_resolv:=list_resolv -l} 410219732Sumeretval=0 411219732Sumefor script in "$LIBEXECDIR"/*; do 412219732Sume if [ -f "$script" ]; then 413219732Sume if [ -x "$script" ]; then 414219732Sume "$script" "$cmd" "$iface" 415219732Sume else 416219732Sume (. "$script" "$cmd" "$iface") 417219732Sume fi 418219732Sume retval=$(($retval + $?)) 419219732Sume fi 420219732Sumedone 421219732Sumeexit $retval 422