a02aba5f3c
Approved by: re (kib)
422 lines
10 KiB
Bash
422 lines
10 KiB
Bash
#!/bin/sh
|
|
# Copyright (c) 2007-2009 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# *}"
|
|
}
|
|
|
|
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 "$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:d: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
|
|
|
|
# -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
|
|
(. "$script" "$cmd" "$iface")
|
|
fi
|
|
retval=$(($retval + $?))
|
|
fi
|
|
done
|
|
exit $retval
|