#!/bin/sh # Copyright (c) 2007-2011 Roy Marples # All rights reserved # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. RESOLVCONF="$0" SYSCONFDIR=@SYSCONFDIR@ LIBEXECDIR=@LIBEXECDIR@ VARDIR=@VARDIR@ # Support original resolvconf configuration layout # as well as the openresolv config file if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then . "$SYSCONFDIR"/resolvconf.conf [ -n "$state_dir" ] && VARDIR="$state_dir" elif [ -d "$SYSCONFDIR/resolvconf" ]; then SYSCONFDIR="$SYSCONFDIR/resolvconf" if [ -f "$SYSCONFDIR"/interface-order ]; then interface_order="$(cat "$SYSCONFDIR"/interface-order)" fi fi IFACEDIR="$VARDIR/interfaces" METRICDIR="$VARDIR/metrics" PRIVATEDIR="$VARDIR/private" : ${dynamic_order:=tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*} : ${interface_order:=lo lo[0-9]*} error_exit() { echo "$*" >&2 exit 1 } usage() { cat <<-EOF Usage: ${RESOLVCONF##*/} [options] Inform the system about any DNS updates. Options: -a \$INTERFACE Add DNS information to the specified interface (DNS supplied via stdin in resolv.conf format) -m metric Give the added DNS information a metric -p Mark the interface as private -d \$INTERFACE Delete DNS information from the specified interface -f Ignore non existant interfaces -I Init the state dir -u Run updates from our current DNS information -l [\$PATTERN] Show DNS information, optionally from interfaces that match the specified pattern -i [\$PATTERN] Show interfaces that have supplied DNS information optionally from interfaces that match the specified pattern -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to the console -h Show this help cruft EOF [ -z "$1" ] && exit 0 echo error_exit "$*" } echo_resolv() { local line= [ -n "$1" -a -e "$IFACEDIR/$1" ] || return 1 echo "# resolv.conf from $1" # Our variable maker works of the fact each resolv.conf per interface # is separated by blank lines. # So we remove them when echoing them. while read line; do [ -n "$line" ] && echo "$line" done < "$IFACEDIR/$1" echo } # Parse resolv.conf's and make variables # for domain name servers, search name servers and global nameservers parse_resolv() { local line= ns= ds= search= d= n= newns= local new=true iface= private=false p= echo "DOMAINS=" echo "SEARCH=\"$search_domains\"" # let our subscribers know about global nameservers for n in $name_servers; do case "$n" in 127.*|0.0.0.0|255.255.255.255|::1) :;; *) newns="$newns${newns:+ }$n";; esac done echo "NAMESERVERS=\"$newns\"" echo "LOCALNAMESERVERS=" newns= while read line; do case "$line" in "# resolv.conf from "*) if ${new}; then iface="${line#\# resolv.conf from *}" new=false if [ -e "$PRIVATEDIR/$iface" ]; then private=true else # Allow expansion cd "$IFACEDIR" private=false for p in $private_interfaces; do if [ "$p" = "$iface" ]; then private=true break fi done fi fi ;; "nameserver "*) case "${line#* }" in 127.*|0.0.0.0|255.255.255.255|::1) echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\"" continue ;; esac ns="$ns${line#* } " ;; "domain "*|"search "*) search="${line#* }" ;; *) [ -n "$line" ] && continue if [ -n "$ns" -a -n "$search" ]; then newns= for n in $ns; do newns="$newns${newns:+,}$n" done ds= for d in $search; do ds="$ds${ds:+ }$d:$newns" done echo "DOMAINS=\"\$DOMAINS $ds\"" fi echo "SEARCH=\"\$SEARCH $search\"" if ! $private; then echo "NAMESERVERS=\"\$NAMESERVERS $ns\"" fi ns= search= new=true ;; esac done } uniqify() { local result= while [ -n "$1" ]; do case " $result " in *" $1 "*);; *) result="$result $1";; esac shift done echo "${result# *}" } dirname() { local dir= OIFS="$IFS" local IFS=/ set -- $@ IFS="$OIFS" if [ -n "$1" ]; then printf %s . else shift fi while [ -n "$2" ]; do printf "/%s" "$1" shift done printf "\n" } config_mkdirs() { local e=0 f d for f; do [ -n "$f" ] || continue d="$(dirname "$f")" if [ ! -d "$d" ]; then if type install >/dev/null 2>&1; then install -d "$d" || e=$? else mkdir "$d" || e=$? fi fi done return $e } list_resolv() { [ -d "$IFACEDIR" ] || return 0 local report=false list= retval=0 cmd="$1" shift # If we have an interface ordering list, then use that. # It works by just using pathname expansion in the interface directory. if [ -n "$1" ]; then list="$*" $force || report=true else cd "$IFACEDIR" for i in $interface_order; do [ -e "$i" ] && list="$list $i" done for i in $dynamic_order; do if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then list="$list $i" fi done if [ -d "$METRICDIR" ]; then cd "$METRICDIR" for i in *; do list="$list ${i#* }" done fi list="$list *" fi cd "$IFACEDIR" for i in $(uniqify $list); do # Only list interfaces which we really have if ! [ -e "$i" ]; then if $report; then echo "No resolv.conf for interface $i" >&2 retval=$(($retval + 1)) fi continue fi if [ "$cmd" = i -o "$cmd" = "-i" ]; then printf %s "$i " else echo_resolv "$i" fi done [ "$cmd" = i -o "$cmd" = "-i" ] && echo return $retval } make_vars() { eval "$(list_resolv -l "$@" | parse_resolv)" # Ensure that we only list each domain once newdomains= for d in $DOMAINS; do dn="${d%%:*}" case " $newdomains" in *" ${dn}:"*) continue;; esac newdomains="$newdomains${newdomains:+ }$dn:" newns= for nd in $DOMAINS; do if [ "$dn" = "${nd%%:*}" ]; then ns="${nd#*:}" while [ -n "$ns" ]; do case ",$newns," in *,${ns%%,*},*) ;; *) newns="$newns${newns:+,}${ns%%,*}";; esac [ "$ns" = "${ns#*,}" ] && break ns="${ns#*,}" done fi done newdomains="$newdomains$newns" done echo "DOMAINS='$newdomains'" echo "SEARCH='$(uniqify $SEARCH)'" echo "NAMESERVERS='$(uniqify $NAMESERVERS)'" echo "LOCALNAMESERVERS='$(uniqify $LOCALNAMESERVERS)'" } force=false while getopts a:Dd:fhIilm:puv OPT; do case "$OPT" in f) force=true;; h) usage;; m) IF_METRIC="$OPTARG";; p) IF_PRIVATE=1;; '?') ;; *) cmd="$OPT"; iface="$OPTARG";; esac done shift $(($OPTIND - 1)) args="$iface${iface:+ }$*" # -I inits the state dir if [ "$cmd" = I ]; then if [ -d "$VARDIR" ]; then rm -rf "$VARDIR"/* fi exit $? fi # -D ensures that the listed config file base dirs exist if [ "$cmd" = D ]; then config_mkdirs "$@" exit $? fi # -l lists our resolv files, optionally for a specific interface if [ "$cmd" = l -o "$cmd" = i ]; then list_resolv "$cmd" "$args" exit $? fi # Not normally needed, but subscribers should be able to run independently if [ "$cmd" = v ]; then make_vars "$iface" exit $? fi # Test that we have valid options if [ "$cmd" = a -o "$cmd" = d ]; then if [ -z "$iface" ]; then usage "Interface not specified" fi elif [ "$cmd" != u ]; then [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd" usage fi if [ "$cmd" = a ]; then for x in '/' \\ ' ' '*'; do case "$iface" in *[$x]*) error_exit "$x not allowed in interface name";; esac done for x in '.' '-' '~'; do case "$iface" in [$x]*) error_exit \ "$x not allowed at start of interface name";; esac done [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin" fi if [ ! -d "$IFACEDIR" ]; then if [ ! -d "$VARDIR" ]; then if [ -L "$VARDIR" ]; then dir="$(readlink "$VARDIR")" # link maybe relative cd "${VARDIR%/*}" if ! mkdir -m 0755 -p "$dir"; then error_exit "Failed to create needed" \ "directory $dir" fi else if ! mkdir -m 0755 -p "$VARDIR"; then error_exit "Failed to create needed" \ "directory $VARDIR" fi fi fi mkdir -m 0755 -p "$IFACEDIR" || \ error_exit "Failed to create needed directory $IFACEDIR" else # Delete any existing information about the interface if [ "$cmd" = d ]; then cd "$IFACEDIR" for i in $args; do if [ "$cmd" = d -a ! -e "$i" ]; then $force && continue error_exit "No resolv.conf for" \ "interface $i" fi rm -f "$i" "$METRICDIR/"*" $i" \ "$PRIVATEDIR/$i" || exit $? done fi fi if [ "$cmd" = a ]; then # Read resolv.conf from stdin resolv="$(cat)" # If what we are given matches what we have, then do nothing if [ -e "$IFACEDIR/$iface" ]; then if [ "$(echo "$resolv")" = \ "$(cat "$IFACEDIR/$iface")" ] then exit 0 fi rm "$IFACEDIR/$iface" fi echo "$resolv" >"$IFACEDIR/$iface" || exit $? [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR" rm -f "$METRICDIR/"*" $iface" if [ -n "$IF_METRIC" ]; then # Pad metric to 6 characters, so 5 is less than 10 while [ ${#IF_METRIC} -le 6 ]; do IF_METRIC="0$IF_METRIC" done echo " " >"$METRICDIR/$IF_METRIC $iface" fi case "$IF_PRIVATE" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) if [ ! -d "$PRIVATEDIR" ]; then [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR" mkdir "$PRIVATEDIR" fi [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface" ;; *) if [ -e "$PRIVATEDIR/$iface" ]; then rm -f "$PRIVATEDIR/$iface" fi ;; esac fi eval "$(make_vars)" export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS : ${list_resolv:=list_resolv -l} retval=0 for script in "$LIBEXECDIR"/*; do if [ -f "$script" ]; then if [ -x "$script" ]; then "$script" "$cmd" "$iface" else (set -- "$cmd" "$iface"; . "$script") fi retval=$(($retval + $?)) fi done exit $retval